<commit_before>#![crate_type="dylib"]
#![feature(plugin_registrar, rustc_private, quote)]

extern crate syntax;
extern crate rustc;
extern crate rustc_plugin;

use rustc_plugin::Registry;
use syntax::ast::{FnDecl, Ident, ItemKind, LitKind, MetaItem};
use syntax::ext::base::{Annotatable, ExtCtxt, SyntaxExtension};
use syntax::codemap::Span;
use syntax::symbol::Symbol;

fn get_js_name(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem) -> String {
    let meta_items = meta_item.meta_item_list().unwrap_or_else(|| {
        ecx.struct_span_err(span, "incorrect use of napi_callback attribute")
            .help("usage: #[napi_callback(\"jsFunctionName\")]")
            .emit();
        ecx.span_fatal(span, "invalid attribute usage");
    });

    if meta_items.len() != 1 {
        ecx.struct_span_err(span, "incorrect use of napi_callback attribute")
            .help("usage: #[napi_callback(\"jsFunctionName\")]")
            .emit();
        ecx.span_fatal(span, "attribute requires exactly one argument");
    }

    let name_meta_item = &meta_items[0];

    let name = name_meta_item
        .literal()
        .and_then(|literal| match literal.node {
            LitKind::Str(ref s, _) => Some(s.as_str()),
            _ => None,
        })
        .unwrap_or_else(|| {
            ecx.span_fatal(
                name_meta_item.span(),
                "attribute argument must be a string",
            );
        });

    String::from(&*name)
}

struct Function {
    pub ident: Ident,
    pub decl: FnDecl,
}

impl Function {
    fn from_annotatable(
        ecx: &mut ExtCtxt,
        annotated: &Annotatable,
    ) -> Function {
        let report_error = |span| {
            ecx.span_fatal(
                span,
                "napi_callback attribute can only be used on \
                functions, but was applied to this item.",
            );
        };

        match *annotated {
            Annotatable::Item(ref item) => {
                match item.node {
                    ItemKind::Fn(ref decl, ..) => Function {
                        ident: item.ident,
                        decl: decl.clone().unwrap(),
                    },
                    _ => report_error(item.span),
                }
            }
            Annotatable::TraitItem(ref item) => report_error(item.span),
            Annotatable::ImplItem(ref item) => report_error(item.span),
        }
    }
}

fn create_napi_callback(ecx: &mut ExtCtxt, function: &Function) -> Annotatable {
    let fn_ident = function.ident;
    let extern_fn_name = format!("napi_rs_cb_{}", fn_ident.name);
    let extern_fn_ident = Ident::from_str(extern_fn_name.as_str());

    Annotatable::Item(
        quote_item!(ecx,
            #[no_mangle]
            pub extern "C" fn $extern_fn_ident(
                env: ::napi::sys::napi_env,
                info: ::napi::sys::napi_callback_info,
            ) -> ::napi::sys::napi_value {
                let result: ::napi::NapiResult<::napi::sys::napi_value> =
                    $fn_ident(env, info);

                match result {
                    Ok(value) => return value,
                    Err(error) => {
                        if let Some(exception) = error.exception {
                            unsafe {
                                ::napi::sys::napi_throw(env, exception);
                            }
                        } else {
                            use std::error::Error;

                            let message = format!("{}", &error);
                            let c_string = std::ffi::CString::new(message)
                                .unwrap_or_else(|_| {
                                    std::ffi::CString::new(
                                        error.description(),
                                    ).unwrap()
                                });

                            unsafe {
                                ::napi::sys::napi_throw_error(
                                    env,
                                    std::ptr::null(),
                                    c_string.as_ptr(),
                                );
                            }
                        }

                        unsafe {
                            let mut result: napi_value =
                                std::mem::uninitialized();
                            ::napi::sys::napi_get_undefined(env, &mut result);
                            result
                        }
                    }
                }
            }
        ).unwrap(),
    )
}

pub fn napi_callback_decorator(
    ecx: &mut ExtCtxt,
    span: Span,
    meta_item: &MetaItem,
    annotated: Annotatable,
) -> Vec<Annotatable> {
    let js_name = get_js_name(ecx, span, meta_item);
    let function = Function::from_annotatable(ecx, &annotated);

    let mut output = Vec::new();

    output.push(create_napi_callback(ecx, &function));
    output.push(annotated);

    output
}

#[plugin_registrar]
pub fn plugin_registrar(registry: &mut Registry) {
    registry.register_syntax_extension(
        Symbol::intern("napi_callback"),
        SyntaxExtension::MultiModifier(Box::new(napi_callback_decorator)),
    );
}
<commit_msg>fix(codegen): always find napi_value<commit_after>#![crate_type="dylib"]
#![feature(plugin_registrar, rustc_private, quote)]

extern crate syntax;
extern crate rustc;
extern crate rustc_plugin;

use rustc_plugin::Registry;
use syntax::ast::{FnDecl, Ident, ItemKind, LitKind, MetaItem};
use syntax::ext::base::{Annotatable, ExtCtxt, SyntaxExtension};
use syntax::codemap::Span;
use syntax::symbol::Symbol;

fn get_js_name(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem) -> String {
    let meta_items = meta_item.meta_item_list().unwrap_or_else(|| {
        ecx.struct_span_err(span, "incorrect use of napi_callback attribute")
            .help("usage: #[napi_callback(\"jsFunctionName\")]")
            .emit();
        ecx.span_fatal(span, "invalid attribute usage");
    });

    if meta_items.len() != 1 {
        ecx.struct_span_err(span, "incorrect use of napi_callback attribute")
            .help("usage: #[napi_callback(\"jsFunctionName\")]")
            .emit();
        ecx.span_fatal(span, "attribute requires exactly one argument");
    }

    let name_meta_item = &meta_items[0];

    let name = name_meta_item
        .literal()
        .and_then(|literal| match literal.node {
            LitKind::Str(ref s, _) => Some(s.as_str()),
            _ => None,
        })
        .unwrap_or_else(|| {
            ecx.span_fatal(
                name_meta_item.span(),
                "attribute argument must be a string",
            );
        });

    String::from(&*name)
}

struct Function {
    pub ident: Ident,
    pub decl: FnDecl,
}

impl Function {
    fn from_annotatable(
        ecx: &mut ExtCtxt,
        annotated: &Annotatable,
    ) -> Function {
        let report_error = |span| {
            ecx.span_fatal(
                span,
                "napi_callback attribute can only be used on \
                functions, but was applied to this item.",
            );
        };

        match *annotated {
            Annotatable::Item(ref item) => {
                match item.node {
                    ItemKind::Fn(ref decl, ..) => Function {
                        ident: item.ident,
                        decl: decl.clone().unwrap(),
                    },
                    _ => report_error(item.span),
                }
            }
            Annotatable::TraitItem(ref item) => report_error(item.span),
            Annotatable::ImplItem(ref item) => report_error(item.span),
        }
    }
}

fn create_napi_callback(ecx: &mut ExtCtxt, function: &Function) -> Annotatable {
    let fn_ident = function.ident;
    let extern_fn_name = format!("napi_rs_cb_{}", fn_ident.name);
    let extern_fn_ident = Ident::from_str(extern_fn_name.as_str());

    Annotatable::Item(
        quote_item!(ecx,
            #[no_mangle]
            pub extern "C" fn $extern_fn_ident(
                env: ::napi::sys::napi_env,
                info: ::napi::sys::napi_callback_info,
            ) -> ::napi::sys::napi_value {
                let result: ::napi::NapiResult<::napi::sys::napi_value> =
                    $fn_ident(env, info);

                match result {
                    Ok(value) => return value,
                    Err(error) => {
                        if let Some(exception) = error.exception {
                            unsafe {
                                ::napi::sys::napi_throw(env, exception);
                            }
                        } else {
                            use std::error::Error;

                            let message = format!("{}", &error);
                            let c_string = std::ffi::CString::new(message)
                                .unwrap_or_else(|_| {
                                    std::ffi::CString::new(
                                        error.description(),
                                    ).unwrap()
                                });

                            unsafe {
                                ::napi::sys::napi_throw_error(
                                    env,
                                    std::ptr::null(),
                                    c_string.as_ptr(),
                                );
                            }
                        }

                        unsafe {
                            let mut result: ::napi::sys::napi_value =
                                std::mem::uninitialized();
                            ::napi::sys::napi_get_undefined(env, &mut result);
                            result
                        }
                    }
                }
            }
        ).unwrap(),
    )
}

pub fn napi_callback_decorator(
    ecx: &mut ExtCtxt,
    span: Span,
    meta_item: &MetaItem,
    annotated: Annotatable,
) -> Vec<Annotatable> {
    let js_name = get_js_name(ecx, span, meta_item);
    let function = Function::from_annotatable(ecx, &annotated);

    let mut output = Vec::new();

    output.push(create_napi_callback(ecx, &function));
    output.push(annotated);

    output
}

#[plugin_registrar]
pub fn plugin_registrar(registry: &mut Registry) {
    registry.register_syntax_extension(
        Symbol::intern("napi_callback"),
        SyntaxExtension::MultiModifier(Box::new(napi_callback_decorator)),
    );
}
<|endoftext|>


<commit_before>//! This module is responsible for resolving paths within rules.

use crate::errors::error;
use crate::{parsing, SsrError};
use parsing::Placeholder;
use ra_db::FilePosition;
use ra_syntax::{ast, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken};
use rustc_hash::{FxHashMap, FxHashSet};
use test_utils::mark;

pub(crate) struct ResolutionScope<'db> {
    scope: hir::SemanticsScope<'db>,
    hygiene: hir::Hygiene,
    node: SyntaxNode,
}

pub(crate) struct ResolvedRule {
    pub(crate) pattern: ResolvedPattern,
    pub(crate) template: Option<ResolvedPattern>,
    pub(crate) index: usize,
}

pub(crate) struct ResolvedPattern {
    pub(crate) placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
    pub(crate) node: SyntaxNode,
    // Paths in `node` that we've resolved.
    pub(crate) resolved_paths: FxHashMap<SyntaxNode, ResolvedPath>,
    pub(crate) ufcs_function_calls: FxHashMap<SyntaxNode, hir::Function>,
    pub(crate) contains_self: bool,
}

pub(crate) struct ResolvedPath {
    pub(crate) resolution: hir::PathResolution,
    /// The depth of the ast::Path that was resolved within the pattern.
    pub(crate) depth: u32,
}

impl ResolvedRule {
    pub(crate) fn new(
        rule: parsing::ParsedRule,
        resolution_scope: &ResolutionScope,
        index: usize,
    ) -> Result<ResolvedRule, SsrError> {
        let resolver =
            Resolver { resolution_scope, placeholders_by_stand_in: rule.placeholders_by_stand_in };
        let resolved_template = if let Some(template) = rule.template {
            Some(resolver.resolve_pattern_tree(template)?)
        } else {
            None
        };
        Ok(ResolvedRule {
            pattern: resolver.resolve_pattern_tree(rule.pattern)?,
            template: resolved_template,
            index,
        })
    }

    pub(crate) fn get_placeholder(&self, token: &SyntaxToken) -> Option<&Placeholder> {
        if token.kind() != SyntaxKind::IDENT {
            return None;
        }
        self.pattern.placeholders_by_stand_in.get(token.text())
    }
}

struct Resolver<'a, 'db> {
    resolution_scope: &'a ResolutionScope<'db>,
    placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
}

impl Resolver<'_, '_> {
    fn resolve_pattern_tree(&self, pattern: SyntaxNode) -> Result<ResolvedPattern, SsrError> {
        use ra_syntax::{SyntaxElement, T};
        let mut resolved_paths = FxHashMap::default();
        self.resolve(pattern.clone(), 0, &mut resolved_paths)?;
        let ufcs_function_calls = resolved_paths
            .iter()
            .filter_map(|(path_node, resolved)| {
                if let Some(grandparent) = path_node.parent().and_then(|parent| parent.parent()) {
                    if grandparent.kind() == SyntaxKind::CALL_EXPR {
                        if let hir::PathResolution::AssocItem(hir::AssocItem::Function(function)) =
                            &resolved.resolution
                        {
                            return Some((grandparent, *function));
                        }
                    }
                }
                None
            })
            .collect();
        let contains_self =
            pattern.descendants_with_tokens().any(|node_or_token| match node_or_token {
                SyntaxElement::Token(t) => t.kind() == T![self],
                _ => false,
            });
        Ok(ResolvedPattern {
            node: pattern,
            resolved_paths,
            placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
            ufcs_function_calls,
            contains_self,
        })
    }

    fn resolve(
        &self,
        node: SyntaxNode,
        depth: u32,
        resolved_paths: &mut FxHashMap<SyntaxNode, ResolvedPath>,
    ) -> Result<(), SsrError> {
        use ra_syntax::ast::AstNode;
        if let Some(path) = ast::Path::cast(node.clone()) {
            if is_self(&path) {
                // Self cannot be resolved like other paths.
                return Ok(());
            }
            // Check if this is an appropriate place in the path to resolve. If the path is
            // something like `a::B::<i32>::c` then we want to resolve `a::B`. If the path contains
            // a placeholder. e.g. `a::$b::c` then we want to resolve `a`.
            if !path_contains_type_arguments(path.qualifier())
                && !self.path_contains_placeholder(&path)
            {
                let resolution = self
                    .resolution_scope
                    .resolve_path(&path)
                    .ok_or_else(|| error!("Failed to resolve path `{}`", node.text()))?;
                resolved_paths.insert(node, ResolvedPath { resolution, depth });
                return Ok(());
            }
        }
        for node in node.children() {
            self.resolve(node, depth + 1, resolved_paths)?;
        }
        Ok(())
    }

    /// Returns whether `path` contains a placeholder, but ignores any placeholders within type
    /// arguments.
    fn path_contains_placeholder(&self, path: &ast::Path) -> bool {
        if let Some(segment) = path.segment() {
            if let Some(name_ref) = segment.name_ref() {
                if self.placeholders_by_stand_in.contains_key(name_ref.text()) {
                    return true;
                }
            }
        }
        if let Some(qualifier) = path.qualifier() {
            return self.path_contains_placeholder(&qualifier);
        }
        false
    }
}

impl<'db> ResolutionScope<'db> {
    pub(crate) fn new(
        sema: &hir::Semantics<'db, ra_ide_db::RootDatabase>,
        resolve_context: FilePosition,
    ) -> ResolutionScope<'db> {
        use ra_syntax::ast::AstNode;
        let file = sema.parse(resolve_context.file_id);
        // Find a node at the requested position, falling back to the whole file.
        let node = file
            .syntax()
            .token_at_offset(resolve_context.offset)
            .left_biased()
            .map(|token| token.parent())
            .unwrap_or_else(|| file.syntax().clone());
        let node = pick_node_for_resolution(node);
        let scope = sema.scope(&node);
        ResolutionScope {
            scope,
            hygiene: hir::Hygiene::new(sema.db, resolve_context.file_id.into()),
            node,
        }
    }

    /// Returns the function in which SSR was invoked, if any.
    pub(crate) fn current_function(&self) -> Option<SyntaxNode> {
        let mut node = self.node.clone();
        loop {
            if node.kind() == SyntaxKind::FN {
                return Some(node);
            }
            node = node.parent()?;
        }
    }

    fn resolve_path(&self, path: &ast::Path) -> Option<hir::PathResolution> {
        let hir_path = hir::Path::from_src(path.clone(), &self.hygiene)?;
        // First try resolving the whole path. This will work for things like
        // `std::collections::HashMap`, but will fail for things like
        // `std::collections::HashMap::new`.
        if let Some(resolution) = self.scope.resolve_hir_path(&hir_path) {
            return Some(resolution);
        }
        // Resolution failed, try resolving the qualifier (e.g. `std::collections::HashMap` and if
        // that succeeds, then iterate through the candidates on the resolved type with the provided
        // name.
        let resolved_qualifier = self.scope.resolve_hir_path_qualifier(&hir_path.qualifier()?)?;
        if let hir::PathResolution::Def(hir::ModuleDef::Adt(adt)) = resolved_qualifier {
            adt.ty(self.scope.db).iterate_path_candidates(
                self.scope.db,
                self.scope.module()?.krate(),
                &FxHashSet::default(),
                Some(hir_path.segments().last()?.name),
                |_ty, assoc_item| Some(hir::PathResolution::AssocItem(assoc_item)),
            )
        } else {
            None
        }
    }
}

fn is_self(path: &ast::Path) -> bool {
    path.segment().map(|segment| segment.self_token().is_some()).unwrap_or(false)
}

/// Returns a suitable node for resolving paths in the current scope. If we create a scope based on
/// a statement node, then we can't resolve local variables that were defined in the current scope
/// (only in parent scopes). So we find another node, ideally a child of the statement where local
/// variable resolution is permitted.
fn pick_node_for_resolution(node: SyntaxNode) -> SyntaxNode {
    match node.kind() {
        SyntaxKind::EXPR_STMT => {
            if let Some(n) = node.first_child() {
                mark::hit!(cursor_after_semicolon);
                return n;
            }
        }
        SyntaxKind::LET_STMT | SyntaxKind::IDENT_PAT => {
            if let Some(next) = node.next_sibling() {
                return pick_node_for_resolution(next);
            }
        }
        SyntaxKind::NAME => {
            if let Some(parent) = node.parent() {
                return pick_node_for_resolution(parent);
            }
        }
        _ => {}
    }
    node
}

/// Returns whether `path` or any of its qualifiers contains type arguments.
fn path_contains_type_arguments(path: Option<ast::Path>) -> bool {
    if let Some(path) = path {
        if let Some(segment) = path.segment() {
            if segment.generic_arg_list().is_some() {
                mark::hit!(type_arguments_within_path);
                return true;
            }
        }
        return path_contains_type_arguments(path.qualifier());
    }
    false
}
<commit_msg>Use SyntaxNode.ancestors instead of a loop<commit_after>//! This module is responsible for resolving paths within rules.

use crate::errors::error;
use crate::{parsing, SsrError};
use parsing::Placeholder;
use ra_db::FilePosition;
use ra_syntax::{ast, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken};
use rustc_hash::{FxHashMap, FxHashSet};
use test_utils::mark;

pub(crate) struct ResolutionScope<'db> {
    scope: hir::SemanticsScope<'db>,
    hygiene: hir::Hygiene,
    node: SyntaxNode,
}

pub(crate) struct ResolvedRule {
    pub(crate) pattern: ResolvedPattern,
    pub(crate) template: Option<ResolvedPattern>,
    pub(crate) index: usize,
}

pub(crate) struct ResolvedPattern {
    pub(crate) placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
    pub(crate) node: SyntaxNode,
    // Paths in `node` that we've resolved.
    pub(crate) resolved_paths: FxHashMap<SyntaxNode, ResolvedPath>,
    pub(crate) ufcs_function_calls: FxHashMap<SyntaxNode, hir::Function>,
    pub(crate) contains_self: bool,
}

pub(crate) struct ResolvedPath {
    pub(crate) resolution: hir::PathResolution,
    /// The depth of the ast::Path that was resolved within the pattern.
    pub(crate) depth: u32,
}

impl ResolvedRule {
    pub(crate) fn new(
        rule: parsing::ParsedRule,
        resolution_scope: &ResolutionScope,
        index: usize,
    ) -> Result<ResolvedRule, SsrError> {
        let resolver =
            Resolver { resolution_scope, placeholders_by_stand_in: rule.placeholders_by_stand_in };
        let resolved_template = if let Some(template) = rule.template {
            Some(resolver.resolve_pattern_tree(template)?)
        } else {
            None
        };
        Ok(ResolvedRule {
            pattern: resolver.resolve_pattern_tree(rule.pattern)?,
            template: resolved_template,
            index,
        })
    }

    pub(crate) fn get_placeholder(&self, token: &SyntaxToken) -> Option<&Placeholder> {
        if token.kind() != SyntaxKind::IDENT {
            return None;
        }
        self.pattern.placeholders_by_stand_in.get(token.text())
    }
}

struct Resolver<'a, 'db> {
    resolution_scope: &'a ResolutionScope<'db>,
    placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
}

impl Resolver<'_, '_> {
    fn resolve_pattern_tree(&self, pattern: SyntaxNode) -> Result<ResolvedPattern, SsrError> {
        use ra_syntax::{SyntaxElement, T};
        let mut resolved_paths = FxHashMap::default();
        self.resolve(pattern.clone(), 0, &mut resolved_paths)?;
        let ufcs_function_calls = resolved_paths
            .iter()
            .filter_map(|(path_node, resolved)| {
                if let Some(grandparent) = path_node.parent().and_then(|parent| parent.parent()) {
                    if grandparent.kind() == SyntaxKind::CALL_EXPR {
                        if let hir::PathResolution::AssocItem(hir::AssocItem::Function(function)) =
                            &resolved.resolution
                        {
                            return Some((grandparent, *function));
                        }
                    }
                }
                None
            })
            .collect();
        let contains_self =
            pattern.descendants_with_tokens().any(|node_or_token| match node_or_token {
                SyntaxElement::Token(t) => t.kind() == T![self],
                _ => false,
            });
        Ok(ResolvedPattern {
            node: pattern,
            resolved_paths,
            placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
            ufcs_function_calls,
            contains_self,
        })
    }

    fn resolve(
        &self,
        node: SyntaxNode,
        depth: u32,
        resolved_paths: &mut FxHashMap<SyntaxNode, ResolvedPath>,
    ) -> Result<(), SsrError> {
        use ra_syntax::ast::AstNode;
        if let Some(path) = ast::Path::cast(node.clone()) {
            if is_self(&path) {
                // Self cannot be resolved like other paths.
                return Ok(());
            }
            // Check if this is an appropriate place in the path to resolve. If the path is
            // something like `a::B::<i32>::c` then we want to resolve `a::B`. If the path contains
            // a placeholder. e.g. `a::$b::c` then we want to resolve `a`.
            if !path_contains_type_arguments(path.qualifier())
                && !self.path_contains_placeholder(&path)
            {
                let resolution = self
                    .resolution_scope
                    .resolve_path(&path)
                    .ok_or_else(|| error!("Failed to resolve path `{}`", node.text()))?;
                resolved_paths.insert(node, ResolvedPath { resolution, depth });
                return Ok(());
            }
        }
        for node in node.children() {
            self.resolve(node, depth + 1, resolved_paths)?;
        }
        Ok(())
    }

    /// Returns whether `path` contains a placeholder, but ignores any placeholders within type
    /// arguments.
    fn path_contains_placeholder(&self, path: &ast::Path) -> bool {
        if let Some(segment) = path.segment() {
            if let Some(name_ref) = segment.name_ref() {
                if self.placeholders_by_stand_in.contains_key(name_ref.text()) {
                    return true;
                }
            }
        }
        if let Some(qualifier) = path.qualifier() {
            return self.path_contains_placeholder(&qualifier);
        }
        false
    }
}

impl<'db> ResolutionScope<'db> {
    pub(crate) fn new(
        sema: &hir::Semantics<'db, ra_ide_db::RootDatabase>,
        resolve_context: FilePosition,
    ) -> ResolutionScope<'db> {
        use ra_syntax::ast::AstNode;
        let file = sema.parse(resolve_context.file_id);
        // Find a node at the requested position, falling back to the whole file.
        let node = file
            .syntax()
            .token_at_offset(resolve_context.offset)
            .left_biased()
            .map(|token| token.parent())
            .unwrap_or_else(|| file.syntax().clone());
        let node = pick_node_for_resolution(node);
        let scope = sema.scope(&node);
        ResolutionScope {
            scope,
            hygiene: hir::Hygiene::new(sema.db, resolve_context.file_id.into()),
            node,
        }
    }

    /// Returns the function in which SSR was invoked, if any.
    pub(crate) fn current_function(&self) -> Option<SyntaxNode> {
        self.node.ancestors().find(|node| node.kind() == SyntaxKind::FN).map(|node| node.clone())
    }

    fn resolve_path(&self, path: &ast::Path) -> Option<hir::PathResolution> {
        let hir_path = hir::Path::from_src(path.clone(), &self.hygiene)?;
        // First try resolving the whole path. This will work for things like
        // `std::collections::HashMap`, but will fail for things like
        // `std::collections::HashMap::new`.
        if let Some(resolution) = self.scope.resolve_hir_path(&hir_path) {
            return Some(resolution);
        }
        // Resolution failed, try resolving the qualifier (e.g. `std::collections::HashMap` and if
        // that succeeds, then iterate through the candidates on the resolved type with the provided
        // name.
        let resolved_qualifier = self.scope.resolve_hir_path_qualifier(&hir_path.qualifier()?)?;
        if let hir::PathResolution::Def(hir::ModuleDef::Adt(adt)) = resolved_qualifier {
            adt.ty(self.scope.db).iterate_path_candidates(
                self.scope.db,
                self.scope.module()?.krate(),
                &FxHashSet::default(),
                Some(hir_path.segments().last()?.name),
                |_ty, assoc_item| Some(hir::PathResolution::AssocItem(assoc_item)),
            )
        } else {
            None
        }
    }
}

fn is_self(path: &ast::Path) -> bool {
    path.segment().map(|segment| segment.self_token().is_some()).unwrap_or(false)
}

/// Returns a suitable node for resolving paths in the current scope. If we create a scope based on
/// a statement node, then we can't resolve local variables that were defined in the current scope
/// (only in parent scopes). So we find another node, ideally a child of the statement where local
/// variable resolution is permitted.
fn pick_node_for_resolution(node: SyntaxNode) -> SyntaxNode {
    match node.kind() {
        SyntaxKind::EXPR_STMT => {
            if let Some(n) = node.first_child() {
                mark::hit!(cursor_after_semicolon);
                return n;
            }
        }
        SyntaxKind::LET_STMT | SyntaxKind::IDENT_PAT => {
            if let Some(next) = node.next_sibling() {
                return pick_node_for_resolution(next);
            }
        }
        SyntaxKind::NAME => {
            if let Some(parent) = node.parent() {
                return pick_node_for_resolution(parent);
            }
        }
        _ => {}
    }
    node
}

/// Returns whether `path` or any of its qualifiers contains type arguments.
fn path_contains_type_arguments(path: Option<ast::Path>) -> bool {
    if let Some(path) = path {
        if let Some(segment) = path.segment() {
            if segment.generic_arg_list().is_some() {
                mark::hit!(type_arguments_within_path);
                return true;
            }
        }
        return path_contains_type_arguments(path.qualifier());
    }
    false
}
<|endoftext|>


<commit_before>use std::cell::Cell;

use super::behaviors::MouseBehavior;
use crate::{
    prelude::*,
    shell::{Key, KeyEvent},
};

#[derive(Clone)]
enum TextBoxAction {
    Key(KeyEvent),
    Mouse(Point),
}

/// The `TextBoxState` handles the text processing of the `TextBox` widget.
pub struct TextBoxState {
    action: RefCell<Option<TextBoxAction>>,
    cursor_x: Cell<f64>,
}

impl Default for TextBoxState {
    fn default() -> Self {
        TextBoxState {
            action: RefCell::new(None),
            cursor_x: Cell::new(0.0),
        }
    }
}

impl TextBoxState {
    fn action(&self, action: TextBoxAction) {
        *self.action.borrow_mut() = Some(action);
    }

    fn handle_key_event(&self, key_event: KeyEvent, ctx: &mut Context<'_>) {
        if !ctx.widget().get::<bool>("focused") {
            return;
        }

        let text = ctx.widget().clone::<String16>("text");
        let mut current_selection = *ctx.child("cursor").get::<TextSelection>("text_selection");

        match key_event.key {
            Key::Left => {
                if let Some(selection) = ctx
                    .child("cursor")
                    .try_get_mut::<TextSelection>("text_selection")
                {
                    selection.start_index =
                        (current_selection.start_index as i32 - 1).max(0) as usize;
                }
            }
            Key::Right => {
                if let Some(selection) = ctx
                    .child("cursor")
                    .try_get_mut::<TextSelection>("text_selection")
                {
                    selection.start_index = (current_selection.start_index + 1).min(text.len());
                }
            }
            Key::Backspace => {
                if !text.is_empty() && current_selection.start_index > 0 {
                    for _ in 0..=current_selection.length {
                        ctx.widget()
                            .get_mut::<String16>("text")
                            .remove(current_selection.start_index - 1);
                        current_selection.start_index =
                            (current_selection.start_index as i32 - 1).max(0) as usize;
                    }

                    if let Some(selection) = ctx
                        .child("cursor")
                        .try_get_mut::<TextSelection>("text_selection")
                    {
                        selection.start_index = current_selection.start_index;
                    }
                }
            }
            Key::Delete => {
                if !text.is_empty() && text.len() < current_selection.start_index {
                    for _ in 0..=current_selection.length {
                        ctx.widget()
                            .get_mut::<String16>("text")
                            .remove(current_selection.start_index);
                    }
                }
            }
            _ => {
                if key_event.text.is_empty() {
                    return;
                }
                ctx.widget()
                    .get_mut::<String16>("text")
                    .insert_str(current_selection.start_index, key_event.text.as_str());

                if let Some(selection) = ctx
                    .child("cursor")
                    .try_get_mut::<TextSelection>("text_selection")
                {
                    selection.start_index =
                        current_selection.start_index + key_event.text.encode_utf16().count();
                }
            }
        }
    }

    fn request_focus(&self, ctx: &mut Context<'_>) {
        let focused_widget = ctx.window().get::<Global>("global").focused_widget;

        if (focused_widget.is_some() && focused_widget.unwrap() == ctx.entity)
            || !ctx.widget().get::<bool>("enabled")
        {
            return;
        }

        if let Some(old_focused_element) = ctx.window().get::<Global>("global").focused_widget {
            let mut old_focused_element = ctx.get_widget(old_focused_element);
            old_focused_element.set("focused", false);
            old_focused_element.update_theme_by_state(false);
        }

        ctx.window().get_mut::<Global>("global").focused_widget = Some(ctx.entity);

        ctx.widget().set("focused", true);
        ctx.widget().update_theme_by_state(false);
        ctx.child("cursor").update_theme_by_state(false);
    }
}

impl State for TextBoxState {
    fn update(&self, ctx: &mut Context<'_>) {
        if let Some(action) = self.action.borrow().clone() {
            match action {
                TextBoxAction::Key(event) => {
                    self.handle_key_event(event, ctx);
                }
                TextBoxAction::Mouse(_p) => {
                    self.request_focus(ctx);
                }
            }
        }

        *self.action.borrow_mut() = None;
        ctx.widget().update_theme_by_state(false);
    }

    fn update_post_layout(&self, ctx: &mut Context<'_>) {
        let mut cursor_x_delta = 0.0;
        let mut scroll_viewer_width = 0.0;

        {
            if let Some(bounds) = ctx
                .child("scroll_viewer")
                .try_get_mut::<Rectangle>("bounds")
            {
                scroll_viewer_width = bounds.width();
            }
        }

        // maybe not use scroll viewer here

        // Adjust offset of text and cursor if cursor position is out of bounds

        {
            let mut cursor = ctx.child("cursor");

            if let Some(margin) = cursor.try_get_mut::<Thickness>("margin") {
                if margin.left() < 0.0 || margin.left() > scroll_viewer_width {
                    cursor_x_delta = self.cursor_x.get() - margin.left();
                    margin.set_left(self.cursor_x.get());
                }
                self.cursor_x.set(margin.left());
            }

            if let Some(bounds) = cursor.try_get_mut::<Rectangle>("bounds") {
                bounds.set_x(self.cursor_x.get());
            }
        }

        if cursor_x_delta != 0.0 {
            {
                if let Some(bounds) = ctx.child("text_block").try_get_mut::<Rectangle>("bounds") {
                    bounds.set_x(bounds.x() + cursor_x_delta);
                }
            }

            if let Some(scroll_offset) = ctx.widget().try_get_mut::<Point>("scroll_offset") {
                scroll_offset.x += cursor_x_delta;
            }
        }
    }
}

widget!(
    /// The `TextBox` widget represents a single line text input widget.
    ///
    /// * CSS element: `text-box`
    TextBox<TextBoxState>: KeyDownHandler {
        /// Sets or shares the text property.
        text: String16,

        /// Sets or shares the water_mark text property.
        water_mark: String16,

        /// Sets or shares the text selection property.
        text_selection: TextSelection,

        /// Sets or shares the foreground property.
        foreground: Brush,

        /// Sets or share the font size property.
        font_size: f64,

        /// Sets or shares the font property.
        font: String,

        /// Sets or shares the background property.
        background: Brush,

        /// Sets or shares the border radius property.
        border_radius: f64,

        /// Sets or shares the border thickness property.
        border_width: Thickness,

        /// Sets or shares the border brush property.
        border_brush: Brush,

        /// Sets or shares the padding property.
        padding: Thickness,

        /// Sets or shares the text offset property.
        scroll_offset: Point,

        /// Sets or shares the (wheel, scroll) delta property.
        delta: Point,

         /// Sets or shares the focused property.
        focused: bool,

        /// Sets or shares the css selector property.
        selector: Selector
    }
);

impl Template for TextBox {
    fn template(self, id: Entity, ctx: &mut BuildContext) -> Self {
        let state = self.clone_state();
        let mouse_state = self.clone_state();

        self.name("TextBox")
            .selector("text-box")
            .text("")
            .foreground(colors::LINK_WATER_COLOR)
            .font_size(fonts::FONT_SIZE_12)
            .font("Roboto Regular")
            .text_selection(TextSelection::default())
            .scroll_offset(0.0)
            .padding(4.0)
            .background(colors::LYNCH_COLOR)
            .border_brush("transparent")
            .border_width(0.0)
            .border_radius(2.0)
            .size(128.0, 32.0)
            .focused(false)
            .delta(0.0)
            .child(
                MouseBehavior::create()
                    .on_mouse_down(move |p| {
                        mouse_state.action(TextBoxAction::Mouse(p));
                        true
                    })
                    .child(
                        Container::create()
                            .background(id)
                            .border_radius(id)
                            .border_width(id)
                            .border_brush(id)
                            .padding(id)
                            .child(
                                Grid::create()
                                    .child(
                                        ScrollViewer::create()
                                            .selector(Selector::default().id("scroll_viewer"))
                                            .scroll_offset(id)
                                            .scroll_viewer_mode(("custom", "disabled"))
                                            .delta(id)
                                            .child(
                                                TextBlock::create()
                                                    .selector(Selector::default().id("text_block"))
                                                    .vertical_alignment("center")
                                                    .foreground(id)
                                                    .text(id)
                                                    .water_mark(id)
                                                    .font(id)
                                                    .font_size(id)
                                                    .build(ctx),
                                            )
                                            .build(ctx),
                                    )
                                    .child(
                                        Cursor::create()
                                            .selector(Selector::from("cursor").id("cursor"))
                                            .margin(0.0)
                                            .horizontal_alignment("start")
                                            .text(id)
                                            .font(id)
                                            .font_size(id)
                                            .scroll_offset(id)
                                            .focused(id)
                                            .text_selection(id)
                                            .build(ctx),
                                    )
                                    .build(ctx),
                            )
                            .build(ctx),
                    )
                    .build(ctx),
            )
            .on_key_down(move |event: KeyEvent| -> bool {
                state.action(TextBoxAction::Key(event));
                false
            })
    }
}
<commit_msg>feat: Add on_activate signal handler to TextBox<commit_after>use std::cell::Cell;

use super::behaviors::MouseBehavior;
use crate::{
    prelude::*,
    shell::{Key, KeyEvent},
};

#[derive(Clone)]
enum TextBoxAction {
    Key(KeyEvent),
    Mouse(Point),
}

/// The `TextBoxState` handles the text processing of the `TextBox` widget.
pub struct TextBoxState {
    action: RefCell<Option<TextBoxAction>>,
    cursor_x: Cell<f64>,
}

impl Default for TextBoxState {
    fn default() -> Self {
        TextBoxState {
            action: RefCell::new(None),
            cursor_x: Cell::new(0.0),
        }
    }
}

impl TextBoxState {
    fn action(&self, action: TextBoxAction) {
        *self.action.borrow_mut() = Some(action);
    }

    fn handle_key_event(&self, key_event: KeyEvent, ctx: &mut Context<'_>) {
        if !ctx.widget().get::<bool>("focused") {
            return;
        }

        let text = ctx.widget().clone::<String16>("text");
        let mut current_selection = *ctx.child("cursor").get::<TextSelection>("text_selection");

        match key_event.key {
            Key::Left => {
                if let Some(selection) = ctx
                    .child("cursor")
                    .try_get_mut::<TextSelection>("text_selection")
                {
                    selection.start_index =
                        (current_selection.start_index as i32 - 1).max(0) as usize;
                }
            }
            Key::Right => {
                if let Some(selection) = ctx
                    .child("cursor")
                    .try_get_mut::<TextSelection>("text_selection")
                {
                    selection.start_index = (current_selection.start_index + 1).min(text.len());
                }
            }
            Key::Backspace => {
                if !text.is_empty() && current_selection.start_index > 0 {
                    for _ in 0..=current_selection.length {
                        ctx.widget()
                            .get_mut::<String16>("text")
                            .remove(current_selection.start_index - 1);
                        current_selection.start_index =
                            (current_selection.start_index as i32 - 1).max(0) as usize;
                    }

                    if let Some(selection) = ctx
                        .child("cursor")
                        .try_get_mut::<TextSelection>("text_selection")
                    {
                        selection.start_index = current_selection.start_index;
                    }
                }
            }
            Key::Delete => {
                if !text.is_empty() && text.len() < current_selection.start_index {
                    for _ in 0..=current_selection.length {
                        ctx.widget()
                            .get_mut::<String16>("text")
                            .remove(current_selection.start_index);
                    }
                }
            }
            _ => {
                if key_event.text.is_empty() {
                    return;
                }
                ctx.widget()
                    .get_mut::<String16>("text")
                    .insert_str(current_selection.start_index, key_event.text.as_str());

                if let Some(selection) = ctx
                    .child("cursor")
                    .try_get_mut::<TextSelection>("text_selection")
                {
                    selection.start_index =
                        current_selection.start_index + key_event.text.encode_utf16().count();
                }
            }
        }
    }

    fn request_focus(&self, ctx: &mut Context<'_>) {
        let focused_widget = ctx.window().get::<Global>("global").focused_widget;

        if (focused_widget.is_some() && focused_widget.unwrap() == ctx.entity)
            || !ctx.widget().get::<bool>("enabled")
        {
            return;
        }

        if let Some(old_focused_element) = ctx.window().get::<Global>("global").focused_widget {
            let mut old_focused_element = ctx.get_widget(old_focused_element);
            old_focused_element.set("focused", false);
            old_focused_element.update_theme_by_state(false);
        }

        ctx.window().get_mut::<Global>("global").focused_widget = Some(ctx.entity);

        ctx.widget().set("focused", true);
        ctx.widget().update_theme_by_state(false);
        ctx.child("cursor").update_theme_by_state(false);
    }
}

impl State for TextBoxState {
    fn update(&self, ctx: &mut Context<'_>) {
        if let Some(action) = self.action.borrow().clone() {
            match action {
                TextBoxAction::Key(event) => {
                    self.handle_key_event(event, ctx);
                }
                TextBoxAction::Mouse(_p) => {
                    self.request_focus(ctx);
                }
            }
        }

        *self.action.borrow_mut() = None;
        ctx.widget().update_theme_by_state(false);
    }

    fn update_post_layout(&self, ctx: &mut Context<'_>) {
        let mut cursor_x_delta = 0.0;
        let mut scroll_viewer_width = 0.0;

        {
            if let Some(bounds) = ctx
                .child("scroll_viewer")
                .try_get_mut::<Rectangle>("bounds")
            {
                scroll_viewer_width = bounds.width();
            }
        }

        // maybe not use scroll viewer here

        // Adjust offset of text and cursor if cursor position is out of bounds

        {
            let mut cursor = ctx.child("cursor");

            if let Some(margin) = cursor.try_get_mut::<Thickness>("margin") {
                if margin.left() < 0.0 || margin.left() > scroll_viewer_width {
                    cursor_x_delta = self.cursor_x.get() - margin.left();
                    margin.set_left(self.cursor_x.get());
                }
                self.cursor_x.set(margin.left());
            }

            if let Some(bounds) = cursor.try_get_mut::<Rectangle>("bounds") {
                bounds.set_x(self.cursor_x.get());
            }
        }

        if cursor_x_delta != 0.0 {
            {
                if let Some(bounds) = ctx.child("text_block").try_get_mut::<Rectangle>("bounds") {
                    bounds.set_x(bounds.x() + cursor_x_delta);
                }
            }

            if let Some(scroll_offset) = ctx.widget().try_get_mut::<Point>("scroll_offset") {
                scroll_offset.x += cursor_x_delta;
            }
        }
    }
}

widget!(
    /// The `TextBox` widget represents a single line text input widget.
    ///
    /// * CSS element: `text-box`
    TextBox<TextBoxState>: KeyDownHandler {
        /// Sets or shares the text property.
        text: String16,

        /// Sets or shares the water_mark text property.
        water_mark: String16,

        /// Sets or shares the text selection property.
        text_selection: TextSelection,

        /// Sets or shares the foreground property.
        foreground: Brush,

        /// Sets or share the font size property.
        font_size: f64,

        /// Sets or shares the font property.
        font: String,

        /// Sets or shares the background property.
        background: Brush,

        /// Sets or shares the border radius property.
        border_radius: f64,

        /// Sets or shares the border thickness property.
        border_width: Thickness,

        /// Sets or shares the border brush property.
        border_brush: Brush,

        /// Sets or shares the padding property.
        padding: Thickness,

        /// Sets or shares the text offset property.
        scroll_offset: Point,

        /// Sets or shares the (wheel, scroll) delta property.
        delta: Point,

         /// Sets or shares the focused property.
        focused: bool,

        /// Sets or shares the css selector property.
        selector: Selector
    }
);

impl TextBox {
    /// The activate signal is emitted when the user hits the Enter key.
    pub fn on_activate<H: Fn() -> bool + 'static>(self, handler: H) -> Self {
        self.on_key_down_key(Key::Enter, handler)
    }
}

impl Template for TextBox {
    fn template(self, id: Entity, ctx: &mut BuildContext) -> Self {
        let state = self.clone_state();
        let mouse_state = self.clone_state();

        self.name("TextBox")
            .selector("text-box")
            .text("")
            .foreground(colors::LINK_WATER_COLOR)
            .font_size(fonts::FONT_SIZE_12)
            .font("Roboto Regular")
            .text_selection(TextSelection::default())
            .scroll_offset(0.0)
            .padding(4.0)
            .background(colors::LYNCH_COLOR)
            .border_brush("transparent")
            .border_width(0.0)
            .border_radius(2.0)
            .size(128.0, 32.0)
            .focused(false)
            .delta(0.0)
            .child(
                MouseBehavior::create()
                    .on_mouse_down(move |p| {
                        mouse_state.action(TextBoxAction::Mouse(p));
                        true
                    })
                    .child(
                        Container::create()
                            .background(id)
                            .border_radius(id)
                            .border_width(id)
                            .border_brush(id)
                            .padding(id)
                            .child(
                                Grid::create()
                                    .child(
                                        ScrollViewer::create()
                                            .selector(Selector::default().id("scroll_viewer"))
                                            .scroll_offset(id)
                                            .scroll_viewer_mode(("custom", "disabled"))
                                            .delta(id)
                                            .child(
                                                TextBlock::create()
                                                    .selector(Selector::default().id("text_block"))
                                                    .vertical_alignment("center")
                                                    .foreground(id)
                                                    .text(id)
                                                    .water_mark(id)
                                                    .font(id)
                                                    .font_size(id)
                                                    .build(ctx),
                                            )
                                            .build(ctx),
                                    )
                                    .child(
                                        Cursor::create()
                                            .selector(Selector::from("cursor").id("cursor"))
                                            .margin(0.0)
                                            .horizontal_alignment("start")
                                            .text(id)
                                            .font(id)
                                            .font_size(id)
                                            .scroll_offset(id)
                                            .focused(id)
                                            .text_selection(id)
                                            .build(ctx),
                                    )
                                    .build(ctx),
                            )
                            .build(ctx),
                    )
                    .build(ctx),
            )
            .on_key_down(move |event: KeyEvent| -> bool {
                state.action(TextBoxAction::Key(event));
                false
            })
    }
}
<|endoftext|>


<commit_before>use core::ptr;
use core::mem;

#[derive(Debug)]
pub struct Link<T> {
    ptr: *mut T,
}

/// Non owner raw link base on *mut pointers
impl<T> Link<T> {
    pub fn none() -> Self {
        Link {
            ptr: ptr::null_mut(),
        }
    }

    pub fn some(ptr: *mut T) -> Self {
        Link {
            ptr: ptr,
        }
    }

    pub fn take(&mut self) -> Link<T> {
        mem::replace(self, Self::none())
    }

    pub fn as_ref(&self) -> Option<&T> {
        if self.ptr.is_null() {
            None
        } else {
            unsafe { Some(& *self.ptr) }
        }
    }

    pub fn as_mut(&mut self) -> Option<&mut T> {
        if self.ptr.is_null() {
            None
        } else {
            unsafe { Some(&mut *self.ptr) }
        }
    }
}

impl<T> PartialEq for Link<T> {
    fn eq(&self, rhs: &Self) -> bool {
        self.ptr == rhs.ptr
    }
}
<commit_msg>libintrusive: list::Link now implements Clone<commit_after>use core::ptr;
use core::mem;

#[derive(Debug)]
pub struct Link<T> {
    ptr: *mut T,
}

/// Non owner raw link base on *mut pointers
impl<T> Link<T> {
    pub fn none() -> Self {
        Link {
            ptr: ptr::null_mut(),
        }
    }

    pub fn some(ptr: *mut T) -> Self {
        Link {
            ptr: ptr,
        }
    }

    pub fn take(&mut self) -> Link<T> {
        mem::replace(self, Self::none())
    }

    pub fn as_ref(&self) -> Option<&T> {
        if self.ptr.is_null() {
            None
        } else {
            unsafe { Some(& *self.ptr) }
        }
    }

    pub fn as_mut(&mut self) -> Option<&mut T> {
        if self.ptr.is_null() {
            None
        } else {
            unsafe { Some(&mut *self.ptr) }
        }
    }
}

impl<T> Clone for Link<T> {
    fn clone(&self) -> Self {
        Link {
            ptr: self.ptr,
        }
    }
}

impl<T> PartialEq for Link<T> {
    fn eq(&self, rhs: &Self) -> bool {
        self.ptr == rhs.ptr
    }
}
<|endoftext|>


<commit_before>//! A module to parse invidual log lines into Requests and Responses

use chrono::*;
use http_status::HttpStatus;

#[derive(Eq, PartialEq, Clone)]
#[derive(Debug)]
pub struct Request {
    pub id: i32,
    pub time: DateTime<FixedOffset>,
    pub url: String,
    pub original_log_line: String
}

impl Request {
    pub fn new_from_log_line(log_line: &String) -> Result<Request, &'static str> {
        let parts: Vec<&str> = log_line.split(" ").collect();

        let id = match parts.get(2) {
            Some(id) =>  id,
            None => return Err("Uncomprehensible request logline")
        };

        // Shortest valid id format is "[1]"
        if id.len() < 3 {
            return Err("Uncomprehensible request logline");
        }

        let id_parsed: i32 = match id[1..id.len()-1].parse() {
            Ok(id) =>  id,
            Err(_) => return Err("Uncomprehensible request logline")
        };

        let url = match parts.get(5) {
            Some(url) =>  url,
            None => return Err("Uncomprehensible request logline")
        };

        let date = &format!("{} {}", parts[0], parts[1]);

        let date_parsed = match DateTime::parse_from_str(date, "%d/%b/%Y:%H:%M:%S %z") {
            Ok(date_time) => date_time,
            Err(_) => return Err("Uncomprehensible request logline")
        };

        Ok(Request {
            id: id_parsed,
            time: date_parsed,
            url: url.to_string(),
            original_log_line: log_line.clone()
        })
    }
}

#[derive(Eq, PartialEq, Clone)]
#[derive(Debug)]
pub struct Response {
    pub id: i32,
    pub time: DateTime<FixedOffset>,
    pub mime_type: String,
    pub response_time: Duration,
    pub http_status: HttpStatus,
    pub original_log_line: String,
}

impl Response {
    pub fn new_from_log_line(log_line: &String) -> Result<Response, &'static str> {
        let parts: Vec<&str> = log_line.split(" ").collect();

        let id = parts[2];

        // Shortest valid id format is "[1]"
        if id.len() < 3 {
            return Err("Uncomprehensible response logline");
        }

        let id_numeric: i32 = match id[1..id.len()-1].parse() {
            Ok(number) => number,
            Err(_) => return Err("Uncomprehensible response logline")
        };

        let time = match DateTime::parse_from_str(&format!("{} {}", parts[0], parts[1]), "%d/%b/%Y:%H:%M:%S %z") {
            Ok(time) => time,
            Err(_) => return Err("Uncomprehensible response logline")
        };

        let response_time = parts[parts.len()-1];
        if response_time.len() < 3 {
            return Err("Uncomprehensible response logline");
        }

        let response_time_duration = match response_time[0..response_time.len()-2].parse() {
            Ok(number) => Duration::milliseconds(number),
            Err(_) => return Err("Uncomprehensible response logline")
        };

        // Handle special case where the mime type sometimes contains
        // a space, so we need to re-assemble it
        let mime_type = match parts.len() {
            8 => format!("{} {}", parts[5], parts[6]),
            _ => parts[5].to_string()
        };

        let status_code = match parts[4].parse() {
            Ok(number) => HttpStatus::from_code(number),
            Err(_) => return Err("Uncomprehensible response logline")
        };

        Ok(Response {
            id: id_numeric,
            time: time,
            response_time: response_time_duration,
            mime_type: mime_type,
            http_status: status_code,
            original_log_line: log_line.clone(),
        })
    }
}

#[cfg(test)]
mod tests {
    use::chrono::*;
    use http_status::HttpStatus;

    use super::*;

    #[test]
    fn test_parse_request_line() {
        let line = "08/Apr/2016:09:58:47 +0200 [02] -> GET /content/some/other.html HTTP/1.1".to_string();

        let expected = Request {
            id: 2,
            time: DateTime::parse_from_str("08/Apr/2016:09:58:47 +0200", "%d/%b/%Y:%H:%M:%S %z").unwrap(),
            url: "/content/some/other.html".to_string(),
            original_log_line: line.clone()
        };

        let result = Request::new_from_log_line(&line);

        assert_eq!(result.unwrap(), expected)
    }

    #[test]
    fn test_parse_request_line_bad_format() {
        let line = "08/A16:09:58:47 justsomegarbage".to_string();

        let expected: Result<Request, &'static str> = Err("Uncomprehensible request logline");
        let result: Result<Request, &'static str> = Request::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_request_line_bad_format_but_enough_parts() {
        let line = "just some garbage with more parts at the end".to_string();

        let expected: Result<Request, &'static str> = Err("Uncomprehensible request logline");
        let result: Result<Request, &'static str> = Request::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_request_line_bad_date_format() {
        let line = "99/XYZ/9999:09:99:99 +9900 [02] -> GET /content/some/other.html HTTP/1.1".to_string();

        let expected: Result<Request, &'static str> = Err("Uncomprehensible request logline");
        let result: Result<Request, &'static str> = Request::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_request_line_bad_id_format() {
        let line = "08/Apr/2016:09:58:47 +0200 2 -> GET /content/some/other.html HTTP/1.1".to_string();

        let expected: Result<Request, &'static str> = Err("Uncomprehensible request logline");
        let result: Result<Request, &'static str> = Request::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line() {
        let line = "08/Apr/2016:09:58:48 +0200 [02] <- 200 text/html 10ms".to_string();

        let expected = Response {
            id: 2,
            time: DateTime::parse_from_str("08/Apr/2016:09:58:48 +0200", "%d/%b/%Y:%H:%M:%S %z").unwrap(),
            mime_type: "text/html".to_string(),
            response_time: Duration::milliseconds(10),
            http_status: HttpStatus::OK,
            original_log_line: line.clone(),
        };

        let result = Response::new_from_log_line(&line);

        assert_eq!(result.unwrap(), expected)
    }

    #[test]
    fn test_parse_response_line_inconsistent_space() {
        let line = "06/Apr/2016:14:54:16 +0200 [200] <- 200 text/html; charset=utf-8 250ms".to_string();

        let expected = Response {
            id: 200,
            time: DateTime::parse_from_str("06/Apr/2016:14:54:16 +0200", "%d/%b/%Y:%H:%M:%S %z").unwrap(),
            mime_type: "text/html; charset=utf-8".to_string(),
            response_time: Duration::milliseconds(250),
            http_status: HttpStatus::OK,
            original_log_line: line.clone(),
        };

        let result = Response::new_from_log_line(&line);

        assert_eq!(result.unwrap(), expected)
    }

    #[test]
    fn test_parse_response_line_bad_id_format() {
        let line = "08/Apr/2016:09:58:48 +0200 2 <- 200 text/html 10ms".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line_bad_id_format_no_number() {
        let line = "08/Apr/2016:09:58:48 +0200 [XXX] <- 200 text/html 10ms".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line_bad_time_format() {
        let line = "08/Apr/2016:5:2X:48 +0200 [83940] <- 200 text/html 14642ms".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line_bad_response_time_too_short() {
        let line = "08/Apr/2016:09:57:47 +0200 [001] <- 200 text/html X".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line_bad_response_time_not_a_number() {
        let line = "08/Apr/2016:09:57:47 +0200 [001] <- 200 text/html XXXms".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line_bad_status_code() {
        let line = "08/Apr/2016:09:57:47 +0200 [001] <- FOO text/html 10ms".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }
}
<commit_msg>Common trait for both Requests and Responses<commit_after>//! A module to parse invidual log lines into Requests and Responses

use chrono::*;
use http_status::HttpStatus;

trait LogEvent {
    fn get_id(&self) -> i32;
}

#[derive(Eq, PartialEq, Clone)]
#[derive(Debug)]
pub struct Request {
    pub id: i32,
    pub time: DateTime<FixedOffset>,
    pub url: String,
    pub original_log_line: String
}

impl Request {
    pub fn new_from_log_line(log_line: &String) -> Result<Request, &'static str> {
        let parts: Vec<&str> = log_line.split(" ").collect();

        let id = match parts.get(2) {
            Some(id) =>  id,
            None => return Err("Uncomprehensible request logline")
        };

        // Shortest valid id format is "[1]"
        if id.len() < 3 {
            return Err("Uncomprehensible request logline");
        }

        let id_parsed: i32 = match id[1..id.len()-1].parse() {
            Ok(id) =>  id,
            Err(_) => return Err("Uncomprehensible request logline")
        };

        let url = match parts.get(5) {
            Some(url) =>  url,
            None => return Err("Uncomprehensible request logline")
        };

        let date = &format!("{} {}", parts[0], parts[1]);

        let date_parsed = match DateTime::parse_from_str(date, "%d/%b/%Y:%H:%M:%S %z") {
            Ok(date_time) => date_time,
            Err(_) => return Err("Uncomprehensible request logline")
        };

        Ok(Request {
            id: id_parsed,
            time: date_parsed,
            url: url.to_string(),
            original_log_line: log_line.clone()
        })
    }
}

impl LogEvent for Request {
    fn get_id(&self) -> i32 {
        self.id
    }
}

#[derive(Eq, PartialEq, Clone)]
#[derive(Debug)]
pub struct Response {
    pub id: i32,
    pub time: DateTime<FixedOffset>,
    pub mime_type: String,
    pub response_time: Duration,
    pub http_status: HttpStatus,
    pub original_log_line: String,
}

impl Response {
    pub fn new_from_log_line(log_line: &String) -> Result<Response, &'static str> {
        let parts: Vec<&str> = log_line.split(" ").collect();

        let id = parts[2];

        // Shortest valid id format is "[1]"
        if id.len() < 3 {
            return Err("Uncomprehensible response logline");
        }

        let id_numeric: i32 = match id[1..id.len()-1].parse() {
            Ok(number) => number,
            Err(_) => return Err("Uncomprehensible response logline")
        };

        let time = match DateTime::parse_from_str(&format!("{} {}", parts[0], parts[1]), "%d/%b/%Y:%H:%M:%S %z") {
            Ok(time) => time,
            Err(_) => return Err("Uncomprehensible response logline")
        };

        let response_time = parts[parts.len()-1];
        if response_time.len() < 3 {
            return Err("Uncomprehensible response logline");
        }

        let response_time_duration = match response_time[0..response_time.len()-2].parse() {
            Ok(number) => Duration::milliseconds(number),
            Err(_) => return Err("Uncomprehensible response logline")
        };

        // Handle special case where the mime type sometimes contains
        // a space, so we need to re-assemble it
        let mime_type = match parts.len() {
            8 => format!("{} {}", parts[5], parts[6]),
            _ => parts[5].to_string()
        };

        let status_code = match parts[4].parse() {
            Ok(number) => HttpStatus::from_code(number),
            Err(_) => return Err("Uncomprehensible response logline")
        };

        Ok(Response {
            id: id_numeric,
            time: time,
            response_time: response_time_duration,
            mime_type: mime_type,
            http_status: status_code,
            original_log_line: log_line.clone(),
        })
    }
}

impl LogEvent for Response {
    fn get_id(&self) -> i32 {
        self.id
    }
}

#[cfg(test)]
mod tests {
    use::chrono::*;
    use http_status::HttpStatus;

    use super::*;

    #[test]
    fn test_parse_request_line() {
        let line = "08/Apr/2016:09:58:47 +0200 [02] -> GET /content/some/other.html HTTP/1.1".to_string();

        let expected = Request {
            id: 2,
            time: DateTime::parse_from_str("08/Apr/2016:09:58:47 +0200", "%d/%b/%Y:%H:%M:%S %z").unwrap(),
            url: "/content/some/other.html".to_string(),
            original_log_line: line.clone()
        };

        let result = Request::new_from_log_line(&line);

        assert_eq!(result.unwrap(), expected)
    }

    #[test]
    fn test_parse_request_line_bad_format() {
        let line = "08/A16:09:58:47 justsomegarbage".to_string();

        let expected: Result<Request, &'static str> = Err("Uncomprehensible request logline");
        let result: Result<Request, &'static str> = Request::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_request_line_bad_format_but_enough_parts() {
        let line = "just some garbage with more parts at the end".to_string();

        let expected: Result<Request, &'static str> = Err("Uncomprehensible request logline");
        let result: Result<Request, &'static str> = Request::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_request_line_bad_date_format() {
        let line = "99/XYZ/9999:09:99:99 +9900 [02] -> GET /content/some/other.html HTTP/1.1".to_string();

        let expected: Result<Request, &'static str> = Err("Uncomprehensible request logline");
        let result: Result<Request, &'static str> = Request::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_request_line_bad_id_format() {
        let line = "08/Apr/2016:09:58:47 +0200 2 -> GET /content/some/other.html HTTP/1.1".to_string();

        let expected: Result<Request, &'static str> = Err("Uncomprehensible request logline");
        let result: Result<Request, &'static str> = Request::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line() {
        let line = "08/Apr/2016:09:58:48 +0200 [02] <- 200 text/html 10ms".to_string();

        let expected = Response {
            id: 2,
            time: DateTime::parse_from_str("08/Apr/2016:09:58:48 +0200", "%d/%b/%Y:%H:%M:%S %z").unwrap(),
            mime_type: "text/html".to_string(),
            response_time: Duration::milliseconds(10),
            http_status: HttpStatus::OK,
            original_log_line: line.clone(),
        };

        let result = Response::new_from_log_line(&line);

        assert_eq!(result.unwrap(), expected)
    }

    #[test]
    fn test_parse_response_line_inconsistent_space() {
        let line = "06/Apr/2016:14:54:16 +0200 [200] <- 200 text/html; charset=utf-8 250ms".to_string();

        let expected = Response {
            id: 200,
            time: DateTime::parse_from_str("06/Apr/2016:14:54:16 +0200", "%d/%b/%Y:%H:%M:%S %z").unwrap(),
            mime_type: "text/html; charset=utf-8".to_string(),
            response_time: Duration::milliseconds(250),
            http_status: HttpStatus::OK,
            original_log_line: line.clone(),
        };

        let result = Response::new_from_log_line(&line);

        assert_eq!(result.unwrap(), expected)
    }

    #[test]
    fn test_parse_response_line_bad_id_format() {
        let line = "08/Apr/2016:09:58:48 +0200 2 <- 200 text/html 10ms".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line_bad_id_format_no_number() {
        let line = "08/Apr/2016:09:58:48 +0200 [XXX] <- 200 text/html 10ms".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line_bad_time_format() {
        let line = "08/Apr/2016:5:2X:48 +0200 [83940] <- 200 text/html 14642ms".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line_bad_response_time_too_short() {
        let line = "08/Apr/2016:09:57:47 +0200 [001] <- 200 text/html X".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line_bad_response_time_not_a_number() {
        let line = "08/Apr/2016:09:57:47 +0200 [001] <- 200 text/html XXXms".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line_bad_status_code() {
        let line = "08/Apr/2016:09:57:47 +0200 [001] <- FOO text/html 10ms".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }
}
<|endoftext|>


<commit_before>use std::cast;
use std::logging;
use std::ptr;
use std::str::raw::from_c_str;
use std::task;

use gtk::*;
use gtk::ffi::*;

use gui;

struct ClockIDWrapper {
    ci: GstClockID,
}

impl ClockIDWrapper {
    fn new(ci: GstClockID) -> ClockIDWrapper {
        ClockIDWrapper { ci: ci }
    }
}

impl Drop for ClockIDWrapper {
    fn drop(&mut self) {
        unsafe {
            gst_clock_id_unschedule(self.ci);
            gst_clock_id_unref(self.ci);
        }
    }
}

struct Player {
    initialized: bool,

    uri_set: bool,
    playing: bool,

    playbin: *mut GstElement,
    report_clock_id: Option<ClockIDWrapper>,
    progress_clock_id: Option<ClockIDWrapper>,
}

impl Player {
    pub fn new() -> Player {
        Player {
            initialized: false,
            uri_set: false,
            playing: false,
            playbin: ptr::mut_null(),
            report_clock_id: None,
            progress_clock_id: None,
        }
    }

    // It's important that the `gui` pointer be constant for the entire duration
    // of the program, as it's sent into the gstreamer lib.
    // I know, this is quite very hacky.
    pub fn init(&mut self, args: ~[~str], gui: &gui::Gui) -> ~[~str] {
        let args2 = unsafe {
            gst_init_with_args(args)
        };
        unsafe {
            "playbin".with_c_str(|c_str| {
                "rusttracks-playbin".with_c_str(|rtpb| {
                    self.playbin = gst_element_factory_make(c_str, rtpb);
                });
            });
            if self.playbin.is_null() {
                fail!("failed to create playbin");
            }

            let bus = gst_pipeline_get_bus(cast::transmute(self.playbin));
            gst_bus_add_watch(bus, bus_callback,
                              cast::transmute::<&gui::Gui, gpointer>(gui));
        }
        self.initialized = true;
        args2
    }

    pub fn set_uri(&mut self, uri: &str, gui: &gui::Gui) {
        self.stop();
        unsafe {
            "uri".with_c_str(|property_c_str| {
                uri.with_c_str(|uri_c_str| {
                    g_object_set(cast::transmute(self.playbin),
                        property_c_str, uri_c_str, ptr::null::<gchar>());
                });
            });
        }

        self.start_report_watch(gui);
        self.start_progress_watch(gui);

        self.uri_set = true;
    }

    fn start_report_watch(&mut self, gui: &gui::Gui) {
        let chan = gui.get_chan().clone();
        unsafe {

            let clock = gst_pipeline_get_clock(cast::transmute(self.playbin));

            // in nanoseconds
            let timeout: guint64 = 30 * 1000 * 1000 * 1000;
            let target_time = gst_clock_get_time(clock) + timeout;

            let ci = gst_clock_new_single_shot_id(clock, target_time);
            self.report_clock_id = Some(ClockIDWrapper::new(ci));

            do task::spawn_sched(task::SingleThreaded) {
                let res = gst_clock_id_wait(ci, ptr::mut_null());
                match res {
                    GST_CLOCK_UNSCHEDULED => { } // Ignore, nothing to do
                    GST_CLOCK_OK => {
                        debug!("30s are up! sending ReportCurrentTrack to gui");
                        chan.send(gui::ReportCurrentTrack);
                    }
                    _ => unreachable!()
                }
            }

            gst_object_unref(cast::transmute(clock));
        }
    }

    fn start_progress_watch(&mut self, gui: &gui::Gui) {
        let chan = gui.get_chan().clone();
        unsafe {

            let clock = gst_pipeline_get_clock(cast::transmute(self.playbin));

            // in nanoseconds
            let period: guint64 = 1 * 1000 * 1000 * 1000;
            let target_time = gst_clock_get_time(clock) + period;

            let ci = gst_clock_new_periodic_id(clock, target_time, period);
            self.progress_clock_id = Some(ClockIDWrapper::new(ci));

            let playbin = self.playbin;
            do task::spawn_sched(task::SingleThreaded) {
                loop {
                    let res = gst_clock_id_wait(ci, ptr::mut_null());
                    match res {
                        GST_CLOCK_UNSCHEDULED => {
                            // Track has ended or whatever, stop polling
                            break;
                        }
                        GST_CLOCK_OK => {
                            debug!("1s is up! sending progress");
                            let mut current_position = 0;
                            let success_position = gst_element_query_position(
                                playbin, GST_FORMAT_TIME, &mut current_position);
                            let mut current_duration = 0;
                            let success_duration = gst_element_query_duration(
                                playbin, GST_FORMAT_TIME, &mut current_duration);

                            if success_duration != 0 && success_position != 0 {
                                chan.send(gui::SetProgress(Some((current_position, current_duration))));
                            } else {
                                chan.send(gui::SetProgress(None));
                            }
                        }
                        _ => unreachable!()
                    }
                }
            }

            gst_object_unref(cast::transmute(clock));
        }
    }

    pub fn play(&mut self) {
        if !self.initialized {
            fail!("player is not initialized");
        }
        if !self.uri_set {
            debug!("uri not set, not playing");
            return;
        }
        unsafe {
            gst_element_set_state(self.playbin, GST_STATE_PLAYING);
        }
        self.playing = true;
    }

    pub fn pause(&mut self) {
        if !self.initialized {
            fail!("player is not initialized");
        }
        unsafe {
            gst_element_set_state(self.playbin, GST_STATE_PAUSED);
        }
        self.playing = false;
    }

    pub fn stop(&mut self) {
        if !self.initialized {
            fail!("player is not initialized");
        }
        self.report_clock_id = None;
        self.progress_clock_id = None;
        unsafe{
            gst_element_set_state(self.playbin, GST_STATE_READY);
        }
        self.uri_set = false;
        self.playing = false;
    }

    pub fn toggle(&mut self) {
        if self.playing {
            self.pause()
        } else {
            self.play()
        }
    }

    pub fn is_playing(&self) -> bool {
        self.playing
    }

    pub fn can_play(&self) -> bool {
        self.uri_set
    }
}

impl Drop for Player {
    fn drop(&mut self) {
        if self.initialized {
            unsafe {
                if !self.playbin.is_null() {
                    gst_element_set_state(self.playbin, GST_STATE_NULL);
                    gst_object_unref(cast::transmute(self.playbin));
                }
                gst_deinit();
            }
        }
    }
}

extern "C" fn bus_callback(_bus: *mut GstBus, msg: *mut GstMessage, data: gpointer) -> gboolean {
    unsafe {
    let gui: &gui::Gui = cast::transmute(data);

    let name = {
        let gst_obj = (*msg).src;
        if gst_obj.is_null() {
            ~"null-source"
        } else {
            let name_ptr = gst_object_get_name(gst_obj);
            if name_ptr.is_null() {
                ~"null-name"
            } else {
                let name = from_c_str(cast::transmute_immut_unsafe(name_ptr));
                g_free(cast::transmute(name_ptr));
                name
            }
        }
    };

    match (*msg)._type {
        GST_MESSAGE_ERROR => {
            let mut err = ptr::mut_null();
            let mut dbg_info = ptr::mut_null();

            gst_message_parse_error(msg, &mut err, &mut dbg_info);

            let err_msg = from_c_str(cast::transmute_immut_unsafe((*err).message));

            error!("ERROR from element {}: {}", name, err_msg);
            error!("Debugging info: {}", from_c_str(cast::transmute_immut_unsafe(dbg_info)));

            gui.get_chan().send(gui::Notify(format!("Playback error: `{}`", err_msg)));

            g_error_free(err);
            g_free(cast::transmute(dbg_info));
        }
        GST_MESSAGE_WARNING => {
            if log_enabled!(logging::WARN) {
                let mut err = ptr::mut_null();
                let mut dbg_info = ptr::mut_null();

                gst_message_parse_error(msg, &mut err, &mut dbg_info);

                warn!("WARNING from element {}: {}", name,
                    from_c_str(cast::transmute_immut_unsafe((*err).message)));
                warn!("Debugging info: {}", from_c_str(cast::transmute_immut_unsafe(dbg_info)));

                g_error_free(err);
                g_free(cast::transmute(dbg_info));
            }
        }
        GST_MESSAGE_INFO => {
            if log_enabled!(logging::INFO) {
                let mut err = ptr::mut_null();
                let mut dbg_info = ptr::mut_null();

                gst_message_parse_error(msg, &mut err, &mut dbg_info);

                info!("INFO from element {}: {}", name,
                    from_c_str(cast::transmute_immut_unsafe((*err).message)));
                info!("Debugging info: {}", from_c_str(cast::transmute_immut_unsafe(dbg_info)));

                g_error_free(err);
                g_free(cast::transmute(dbg_info));
            }
        }
        GST_MESSAGE_EOS => {
            debug!("EOS from element {}", name);
            gui.get_chan().send(gui::NextTrack);
        }
        _ => {
            if log_enabled!(logging::DEBUG) {
                let msg_type_cstr = gst_message_type_get_name((*msg)._type);
                let msg_type_name = ::std::str::raw::from_c_str(msg_type_cstr);
                debug!("message of type `{}` from element `{}`", msg_type_name, name);
            }
        }
    }

    // Returning 0 removes this callback
    return 1;
    }
}
<commit_msg>player: more immediate progress updates when starting to play a track<commit_after>use std::cast;
use std::logging;
use std::ptr;
use std::str::raw::from_c_str;
use std::task;

use gtk::*;
use gtk::ffi::*;

use gui;

struct ClockIDWrapper {
    ci: GstClockID,
}

impl ClockIDWrapper {
    fn new(ci: GstClockID) -> ClockIDWrapper {
        ClockIDWrapper { ci: ci }
    }
}

impl Drop for ClockIDWrapper {
    fn drop(&mut self) {
        unsafe {
            gst_clock_id_unschedule(self.ci);
            gst_clock_id_unref(self.ci);
        }
    }
}

struct Player {
    initialized: bool,

    uri_set: bool,
    playing: bool,

    playbin: *mut GstElement,
    report_clock_id: Option<ClockIDWrapper>,
    progress_clock_id: Option<ClockIDWrapper>,
}

impl Player {
    pub fn new() -> Player {
        Player {
            initialized: false,
            uri_set: false,
            playing: false,
            playbin: ptr::mut_null(),
            report_clock_id: None,
            progress_clock_id: None,
        }
    }

    // It's important that the `gui` pointer be constant for the entire duration
    // of the program, as it's sent into the gstreamer lib.
    // I know, this is quite very hacky.
    pub fn init(&mut self, args: ~[~str], gui: &gui::Gui) -> ~[~str] {
        let args2 = unsafe {
            gst_init_with_args(args)
        };
        unsafe {
            "playbin".with_c_str(|c_str| {
                "rusttracks-playbin".with_c_str(|rtpb| {
                    self.playbin = gst_element_factory_make(c_str, rtpb);
                });
            });
            if self.playbin.is_null() {
                fail!("failed to create playbin");
            }

            let bus = gst_pipeline_get_bus(cast::transmute(self.playbin));
            gst_bus_add_watch(bus, bus_callback,
                              cast::transmute::<&gui::Gui, gpointer>(gui));
        }
        self.initialized = true;
        args2
    }

    pub fn set_uri(&mut self, uri: &str, gui: &gui::Gui) {
        self.stop();
        unsafe {
            "uri".with_c_str(|property_c_str| {
                uri.with_c_str(|uri_c_str| {
                    g_object_set(cast::transmute(self.playbin),
                        property_c_str, uri_c_str, ptr::null::<gchar>());
                });
            });
        }

        self.start_report_watch(gui);
        self.start_progress_watch(gui);

        self.uri_set = true;
    }

    fn start_report_watch(&mut self, gui: &gui::Gui) {
        let chan = gui.get_chan().clone();
        unsafe {

            let clock = gst_pipeline_get_clock(cast::transmute(self.playbin));

            // in nanoseconds
            let timeout: guint64 = 30 * 1000 * 1000 * 1000;
            let target_time = gst_clock_get_time(clock) + timeout;

            let ci = gst_clock_new_single_shot_id(clock, target_time);
            self.report_clock_id = Some(ClockIDWrapper::new(ci));

            do task::spawn_sched(task::SingleThreaded) {
                let res = gst_clock_id_wait(ci, ptr::mut_null());
                match res {
                    GST_CLOCK_UNSCHEDULED => { } // Ignore, nothing to do
                    GST_CLOCK_OK => {
                        debug!("30s are up! sending ReportCurrentTrack to gui");
                        chan.send(gui::ReportCurrentTrack);
                    }
                    _ => unreachable!()
                }
            }

            gst_object_unref(cast::transmute(clock));
        }
    }

    fn start_progress_watch(&mut self, gui: &gui::Gui) {
        let chan = gui.get_chan().clone();
        unsafe {

            let clock = gst_pipeline_get_clock(cast::transmute(self.playbin));

            // in nanoseconds
            let period: guint64 = 1 * 1000 * 1000 * 1000;
            let start_time = gst_clock_get_time(clock);

            let ci = gst_clock_new_periodic_id(clock, start_time, period);
            self.progress_clock_id = Some(ClockIDWrapper::new(ci));

            let playbin = self.playbin;
            do task::spawn_sched(task::SingleThreaded) {
                loop {
                    let res = gst_clock_id_wait(ci, ptr::mut_null());
                    match res {
                        GST_CLOCK_UNSCHEDULED => {
                            // Track has ended or whatever, stop polling
                            break;
                        }
                        // early is ok as well, we just want the current time
                        GST_CLOCK_OK | GST_CLOCK_EARLY => {
                            debug!("1s is up! sending progress");
                            let mut current_position = 0;
                            let success_position = gst_element_query_position(
                                playbin, GST_FORMAT_TIME, &mut current_position);
                            let mut current_duration = 0;
                            let success_duration = gst_element_query_duration(
                                playbin, GST_FORMAT_TIME, &mut current_duration);

                            if success_duration != 0 && success_position != 0 {
                                chan.send(gui::SetProgress(Some((current_position, current_duration))));
                            } else {
                                chan.send(gui::SetProgress(None));
                            }
                        }
                        _ => unreachable!()
                    }
                }
            }

            gst_object_unref(cast::transmute(clock));
        }
    }

    pub fn play(&mut self) {
        if !self.initialized {
            fail!("player is not initialized");
        }
        if !self.uri_set {
            debug!("uri not set, not playing");
            return;
        }
        unsafe {
            gst_element_set_state(self.playbin, GST_STATE_PLAYING);
        }
        self.playing = true;
    }

    pub fn pause(&mut self) {
        if !self.initialized {
            fail!("player is not initialized");
        }
        unsafe {
            gst_element_set_state(self.playbin, GST_STATE_PAUSED);
        }
        self.playing = false;
    }

    pub fn stop(&mut self) {
        if !self.initialized {
            fail!("player is not initialized");
        }
        self.report_clock_id = None;
        self.progress_clock_id = None;
        unsafe{
            gst_element_set_state(self.playbin, GST_STATE_READY);
        }
        self.uri_set = false;
        self.playing = false;
    }

    pub fn toggle(&mut self) {
        if self.playing {
            self.pause()
        } else {
            self.play()
        }
    }

    pub fn is_playing(&self) -> bool {
        self.playing
    }

    pub fn can_play(&self) -> bool {
        self.uri_set
    }
}

impl Drop for Player {
    fn drop(&mut self) {
        if self.initialized {
            unsafe {
                if !self.playbin.is_null() {
                    gst_element_set_state(self.playbin, GST_STATE_NULL);
                    gst_object_unref(cast::transmute(self.playbin));
                }
                gst_deinit();
            }
        }
    }
}

extern "C" fn bus_callback(_bus: *mut GstBus, msg: *mut GstMessage, data: gpointer) -> gboolean {
    unsafe {
    let gui: &gui::Gui = cast::transmute(data);

    let name = {
        let gst_obj = (*msg).src;
        if gst_obj.is_null() {
            ~"null-source"
        } else {
            let name_ptr = gst_object_get_name(gst_obj);
            if name_ptr.is_null() {
                ~"null-name"
            } else {
                let name = from_c_str(cast::transmute_immut_unsafe(name_ptr));
                g_free(cast::transmute(name_ptr));
                name
            }
        }
    };

    match (*msg)._type {
        GST_MESSAGE_ERROR => {
            let mut err = ptr::mut_null();
            let mut dbg_info = ptr::mut_null();

            gst_message_parse_error(msg, &mut err, &mut dbg_info);

            let err_msg = from_c_str(cast::transmute_immut_unsafe((*err).message));

            error!("ERROR from element {}: {}", name, err_msg);
            error!("Debugging info: {}", from_c_str(cast::transmute_immut_unsafe(dbg_info)));

            gui.get_chan().send(gui::Notify(format!("Playback error: `{}`", err_msg)));

            g_error_free(err);
            g_free(cast::transmute(dbg_info));
        }
        GST_MESSAGE_WARNING => {
            if log_enabled!(logging::WARN) {
                let mut err = ptr::mut_null();
                let mut dbg_info = ptr::mut_null();

                gst_message_parse_error(msg, &mut err, &mut dbg_info);

                warn!("WARNING from element {}: {}", name,
                    from_c_str(cast::transmute_immut_unsafe((*err).message)));
                warn!("Debugging info: {}", from_c_str(cast::transmute_immut_unsafe(dbg_info)));

                g_error_free(err);
                g_free(cast::transmute(dbg_info));
            }
        }
        GST_MESSAGE_INFO => {
            if log_enabled!(logging::INFO) {
                let mut err = ptr::mut_null();
                let mut dbg_info = ptr::mut_null();

                gst_message_parse_error(msg, &mut err, &mut dbg_info);

                info!("INFO from element {}: {}", name,
                    from_c_str(cast::transmute_immut_unsafe((*err).message)));
                info!("Debugging info: {}", from_c_str(cast::transmute_immut_unsafe(dbg_info)));

                g_error_free(err);
                g_free(cast::transmute(dbg_info));
            }
        }
        GST_MESSAGE_EOS => {
            debug!("EOS from element {}", name);
            gui.get_chan().send(gui::NextTrack);
        }
        _ => {
            if log_enabled!(logging::DEBUG) {
                let msg_type_cstr = gst_message_type_get_name((*msg)._type);
                let msg_type_name = ::std::str::raw::from_c_str(msg_type_cstr);
                debug!("message of type `{}` from element `{}`", msg_type_name, name);
            }
        }
    }

    // Returning 0 removes this callback
    return 1;
    }
}
<|endoftext|>


<commit_before>// xfail-test - this isn't really a test.

 {

// select!
macro_rules! select_if (

    {
        $index:expr,
        $count:expr
    } => {
        fail
    };

    {
        $index:expr,
        $count:expr,
        $port:path => [
            $(type_this $message:path$(($(x $x: ident),+))dont_type_this*
              -> $next:ident => { $e:expr }),+
        ]
        $(, $ports:path => [
            $(type_this $messages:path$(($(x $xs: ident),+))dont_type_this*
              -> $nexts:ident => { $es:expr }),+
        ] )*
    } => {
        if $index == $count {
            match move pipes::try_recv($port) {
              $(Some($message($($(ref $x,)+)* ref next)) => {
                // FIXME (#2329) we really want move out of enum here.
                let $next = unsafe { let x <- *ptr::addr_of(&(*next)); x };
                $e
              })+
              _ => fail
            }
        } else {
            select_if!(
                $index,
                $count + 1
                $(, $ports => [
                    $(type_this $messages$(($(x $xs),+))dont_type_this*
                      -> $nexts => { $es }),+
                ])*
            )
        }
    };
)

macro_rules! select (
    {
        $( $port:path => {
            $($message:path$(($($x: ident),+))dont_type_this*
              -> $next:ident $e:expr),+
        } )+
    } => {
        let index = pipes::selecti([$(($port).header()),+]/_);
        select_if!(index, 0 $(, $port => [
            $(type_this $message$(($(x $x),+))dont_type_this* -> $next => { $e }),+
        ])+)
    }
)

}
<commit_msg>Use match move in select-macro, get rid of a FIXME<commit_after>// xfail-test - this isn't really a test.

 {

// select!
macro_rules! select_if (

    {
        $index:expr,
        $count:expr
    } => {
        fail
    };

    {
        $index:expr,
        $count:expr,
        $port:path => [
            $(type_this $message:path$(($(x $x: ident),+))dont_type_this*
              -> $next:ident => { $e:expr }),+
        ]
        $(, $ports:path => [
            $(type_this $messages:path$(($(x $xs: ident),+))dont_type_this*
              -> $nexts:ident => { $es:expr }),+
        ] )*
    } => {
        if $index == $count {
            match move pipes::try_recv($port) {
              $(Some($message($($(ref $x,)+)* move next)) => {
                let $next = next;
                $e
              })+
              _ => fail
            }
        } else {
            select_if!(
                $index,
                $count + 1
                $(, $ports => [
                    $(type_this $messages$(($(x $xs),+))dont_type_this*
                      -> $nexts => { $es }),+
                ])*
            )
        }
    };
)

macro_rules! select (
    {
        $( $port:path => {
            $($message:path$(($($x: ident),+))dont_type_this*
              -> $next:ident $e:expr),+
        } )+
    } => {
        let index = pipes::selecti([$(($port).header()),+]/_);
        select_if!(index, 0 $(, $port => [
            $(type_this $message$(($(x $x),+))dont_type_this* -> $next => { $e }),+
        ])+)
    }
)

}
<|endoftext|>