text:
pub mod attr;
pub mod derive;

use std::collections::HashMap;

use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::{
    ext::IdentExt as _,
    parse::{Parse, ParseStream},
    parse_quote,
    spanned::Spanned as _,
    token,
};

use crate::common::{
    filter_attrs, gen,
    parse::{
        attr::{err, OptionExt as _},
        ParseBufferExt as _,
    },
    scalar, Description, SpanContainer,
};

/// Helper alias for the type of [`Attr::external_resolvers`] field.
type AttrResolvers = HashMap<syn::Type, SpanContainer<syn::ExprPath>>;

/// Available arguments behind `#[graphql]` (or `#[graphql_union]`) attribute
/// when generating code for [GraphQL union][1] type.
///
/// [1]: https://spec.graphql.org/October2021#sec-Unions
#[derive(Debug, Default)]
struct Attr {
    /// Explicitly specified name of [GraphQL union][1] type.
    ///
    /// If [`None`], then Rust type name is used by default.
    ///
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    name: Option<SpanContainer<String>>,

    /// Explicitly specified [description][2] of [GraphQL union][1] type.
    ///
    /// If [`None`], then Rust doc comment will be used as the [description][2],
    /// if any.
    ///
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    /// [2]: https://spec.graphql.org/October2021#sec-Descriptions
    description: Option<SpanContainer<Description>>,

    /// Explicitly specified type of [`Context`] to use for resolving this
    /// [GraphQL union][1] type with.
    ///
    /// If [`None`], then unit type `()` is assumed as a type of [`Context`].
    ///
    /// [`Context`]: juniper::Context
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    context: Option<SpanContainer<syn::Type>>,

    /// Explicitly specified type of [`ScalarValue`] to use for resolving this
    /// [GraphQL union][1] type with.
    ///
    /// If [`None`], then generated code will be generic over any
    /// [`ScalarValue`] type, which, in turn, requires all [union][1] variants
    /// to be generic over any [`ScalarValue`] type too. That's why this type
    /// should be specified only if one of the variants implements
    /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type.
    ///
    /// [`GraphQLType`]: juniper::GraphQLType
    /// [`ScalarValue`]: juniper::ScalarValue
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    scalar: Option<SpanContainer<scalar::AttrValue>>,

    /// Explicitly specified external resolver functions for [GraphQL union][1]
    /// variants.
    ///
    /// If [`None`], then macro will try to auto-infer all the possible variants
    /// from the type declaration, if possible. That's why specifying an
    /// external resolver function has sense, when some custom [union][1]
    /// variant resolving logic is involved, or variants cannot be inferred.
    ///
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    external_resolvers: AttrResolvers,

    /// Indicator whether the generated code is intended to be used only inside
    /// the [`juniper`] library.
    is_internal: bool,
}

impl Parse for Attr {
    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
        let mut out = Self::default();
        while !input.is_empty() {
            let ident = input.parse::<syn::Ident>()?;
            match ident.to_string().as_str() {
                "name" => {
                    input.parse::<token::Eq>()?;
                    let name = input.parse::<syn::LitStr>()?;
                    out.name
                        .replace(SpanContainer::new(
                            ident.span(),
                            Some(name.span()),
                            name.value(),
                        ))
                        .none_or_else(|_| err::dup_arg(&ident))?
                }
                "desc" | "description" => {
                    input.parse::<token::Eq>()?;
                    let desc = input.parse::<Description>()?;
                    out.description
                        .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
                        .none_or_else(|_| err::dup_arg(&ident))?
                }
                "ctx" | "context" | "Context" => {
                    input.parse::<token::Eq>()?;
                    let ctx = input.parse::<syn::Type>()?;
                    out.context
                        .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx))
                        .none_or_else(|_| err::dup_arg(&ident))?
                }
                "scalar" | "Scalar" | "ScalarValue" => {
                    input.parse::<token::Eq>()?;
                    let scl = input.parse::<scalar::AttrValue>()?;
                    out.scalar
                        .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
                        .none_or_else(|_| err::dup_arg(&ident))?
                }
                "on" => {
                    let ty = input.parse::<syn::Type>()?;
                    input.parse::<token::Eq>()?;
                    let rslvr = input.parse::<syn::ExprPath>()?;
                    let rslvr_spanned = SpanContainer::new(ident.span(), Some(ty.span()), rslvr);
                    let rslvr_span = rslvr_spanned.span_joined();
                    out.external_resolvers
                        .insert(ty, rslvr_spanned)
                        .none_or_else(|_| err::dup_arg(rslvr_span))?
                }
                "internal" => {
                    out.is_internal = true;
                }
                name => {
                    return Err(err::unknown_arg(&ident, name));
                }
            }
            input.try_parse::<token::Comma>()?;
        }
        Ok(out)
    }
}

impl Attr {
    /// Tries to merge two [`Attr`]s into a single one, reporting about
    /// duplicates, if any.
    fn try_merge(self, mut another: Self) -> syn::Result<Self> {
        Ok(Self {
            name: try_merge_opt!(name: self, another),
            description: try_merge_opt!(description: self, another),
            context: try_merge_opt!(context: self, another),
            scalar: try_merge_opt!(scalar: self, another),
            external_resolvers: try_merge_hashmap!(
                external_resolvers: self, another => span_joined
            ),
            is_internal: self.is_internal || another.is_internal,
        })
    }

    /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s
    /// placed on a type definition.
    fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
        let mut meta = filter_attrs(name, attrs)
            .map(|attr| attr.parse_args())
            .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;

        if meta.description.is_none() {
            meta.description = Description::parse_from_doc_attrs(attrs)?;
        }

        Ok(meta)
    }
}

/// Available arguments behind `#[graphql]` attribute when generating code for
/// [GraphQL union][1]'s variant.
///
/// [1]: https://spec.graphql.org/October2021#sec-Unions
#[derive(Debug, Default)]
struct VariantAttr {
    /// Explicitly specified marker for the variant/field being ignored and not
    /// included into [GraphQL union][1].
    ///
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    ignore: Option<SpanContainer<syn::Ident>>,

    /// Explicitly specified external resolver function for this [GraphQL union][1] variant.
    ///
    /// If absent, then macro will generate the code which just returns the variant inner value.
    /// Usually, specifying an external resolver function has sense, when some custom resolving
    /// logic is involved.
    ///
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    external_resolver: Option<SpanContainer<syn::ExprPath>>,
}

impl Parse for VariantAttr {
    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
        let mut out = Self::default();
        while !input.is_empty() {
            let ident = input.parse::<syn::Ident>()?;
            match ident.to_string().as_str() {
                "ignore" | "skip" => out
                    .ignore
                    .replace(SpanContainer::new(ident.span(), None, ident.clone()))
                    .none_or_else(|_| err::dup_arg(&ident))?,
                "with" => {
                    input.parse::<token::Eq>()?;
                    let rslvr = input.parse::<syn::ExprPath>()?;
                    out.external_resolver
                        .replace(SpanContainer::new(ident.span(), Some(rslvr.span()), rslvr))
                        .none_or_else(|_| err::dup_arg(&ident))?
                }
                name => {
                    return Err(err::unknown_arg(&ident, name));
                }
            }
            input.try_parse::<token::Comma>()?;
        }
        Ok(out)
    }
}

impl VariantAttr {
    /// Tries to merge two [`VariantAttr`]s into a single one, reporting about
    /// duplicates, if any.
    fn try_merge(self, mut another: Self) -> syn::Result<Self> {
        Ok(Self {
            ignore: try_merge_opt!(ignore: self, another),
            external_resolver: try_merge_opt!(external_resolver: self, another),
        })
    }

    /// Parses [`VariantAttr`] from the given multiple `name`d
    /// [`syn::Attribute`]s placed on a variant/field/method definition.
    fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
        filter_attrs(name, attrs)
            .map(|attr| attr.parse_args())
            .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))
    }
}

/// Definition of [GraphQL union][1] for code generation.
///
/// [1]: https://spec.graphql.org/October2021#sec-Unions
struct Definition {
    /// Name of this [GraphQL union][1] in GraphQL schema.
    ///
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    name: String,

    /// Rust type that this [GraphQL union][1] is represented with.
    ///
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    ty: syn::Type,

    /// Generics of the Rust type that this [GraphQL union][1] is implemented
    /// for.
    ///
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    generics: syn::Generics,

    /// Indicator whether code should be generated for a trait object, rather
    /// than for a regular Rust type.
    is_trait_object: bool,

    /// Description of this [GraphQL union][1] to put into GraphQL schema.
    ///
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    description: Option<Description>,

    /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
    /// for this [GraphQL union][1].
    ///
    /// [`Context`]: juniper::Context
    /// [`GraphQLType`]: juniper::GraphQLType
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    context: syn::Type,

    /// Rust type of [`ScalarValue`] to generate [`GraphQLType`] implementation
    /// with for this [GraphQL union][1].
    ///
    /// If [`None`] then generated code will be generic over any [`ScalarValue`]
    /// type, which, in turn, requires all [union][1] variants to be generic
    /// over any [`ScalarValue`] type too. That's why this type should be
    /// specified only if one of the variants implements [`GraphQLType`] in a
    /// non-generic way over [`ScalarValue`] type.
    ///
    /// [`GraphQLType`]: juniper::GraphQLType
    /// [`ScalarValue`]: juniper::ScalarValue
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    scalar: scalar::Type,

    /// Variants definitions of this [GraphQL union][1].
    ///
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    variants: Vec<VariantDefinition>,
}

impl ToTokens for Definition {
    fn to_tokens(&self, into: &mut TokenStream) {
        self.impl_graphql_union_tokens().to_tokens(into);
        self.impl_output_type_tokens().to_tokens(into);
        self.impl_graphql_type_tokens().to_tokens(into);
        self.impl_graphql_value_tokens().to_tokens(into);
        self.impl_graphql_value_async_tokens().to_tokens(into);
        self.impl_reflection_traits_tokens().to_tokens(into);
    }
}

impl Definition {
    /// Returns prepared [`syn::Generics::split_for_impl`] for [`GraphQLType`]
    /// trait (and similar) implementation of this [GraphQL union][1].
    ///
    /// If `for_async` is `true`, then additional predicates are added to suit
    /// the [`GraphQLAsyncValue`] trait (and similar) requirements.
    ///
    /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue
    /// [`GraphQLType`]: juniper::GraphQLType
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    #[must_use]
    fn impl_generics(
        &self,
        for_async: bool,
    ) -> (TokenStream, TokenStream, Option<syn::WhereClause>) {
        let (_, ty_generics, _) = self.generics.split_for_impl();
        let ty = &self.ty;

        let mut ty_full = quote! { #ty #ty_generics };
        if self.is_trait_object {
            ty_full = quote! { dyn #ty_full + '__obj + Send + Sync };
        }

        let mut generics = self.generics.clone();

        if self.is_trait_object {
            generics.params.push(parse_quote! { '__obj });
        }

        let scalar = &self.scalar;
        if scalar.is_implicit_generic() {
            generics.params.push(parse_quote! { #scalar });
        }
        if scalar.is_generic() {
            generics
                .make_where_clause()
                .predicates
                .push(parse_quote! { #scalar: ::juniper::ScalarValue });
        }
        if let Some(bound) = scalar.bounds() {
            generics.make_where_clause().predicates.push(bound);
        }

        if for_async {
            let self_ty = if !self.is_trait_object && self.generics.lifetimes().next().is_some() {
                // Modify lifetime names to omit "lifetime name `'a` shadows a
                // lifetime name that is already in scope" error.
                let mut generics = self.generics.clone();
                for lt in generics.lifetimes_mut() {
                    let ident = lt.lifetime.ident.unraw();
                    lt.lifetime.ident = format_ident!("__fa__{ident}");
                }

                let lifetimes = generics.lifetimes().map(|lt| &lt.lifetime);
                let ty = &self.ty;
                let (_, ty_generics, _) = generics.split_for_impl();

                quote! { for<#( #lifetimes ),*> #ty #ty_generics }
            } else {
                quote! { Self }
            };
            generics
                .make_where_clause()
                .predicates
                .push(parse_quote! { #self_ty: Sync });

            if scalar.is_generic() {
                generics
                    .make_where_clause()
                    .predicates
                    .push(parse_quote! { #scalar: Send + Sync });
            }
        }

        let (impl_generics, _, where_clause) = generics.split_for_impl();
        (
            quote! { #impl_generics },
            quote! { #ty_full },
            where_clause.cloned(),
        )
    }

    /// Returns generated code implementing [`GraphQLUnion`] trait for this
    /// [GraphQL union][1].
    ///
    /// [`GraphQLUnion`]: juniper::GraphQLUnion
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    #[must_use]
    fn impl_graphql_union_tokens(&self) -> TokenStream {
        let scalar = &self.scalar;

        let (impl_generics, ty_full, where_clause) = self.impl_generics(false);

        let variant_tys: Vec<_> = self.variants.iter().map(|var| &var.ty).collect();
        let all_variants_unique = (variant_tys.len() > 1).then(|| {
            quote! { ::juniper::sa::assert_type_ne_all!(#( #variant_tys ),*); }
        });

        quote! {
            #[automatically_derived]
            impl #impl_generics ::juniper::marker::GraphQLUnion<#scalar> for #ty_full #where_clause
            {
                fn mark() {
                    #all_variants_unique
                    #( <#variant_tys as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )*
                }
            }
        }
    }

    /// Returns generated code implementing [`marker::IsOutputType`] trait for
    /// this [GraphQL union][1].
    ///
    /// [`marker::IsOutputType`]: juniper::marker::IsOutputType
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    #[must_use]
    fn impl_output_type_tokens(&self) -> TokenStream {
        let scalar = &self.scalar;

        let (impl_generics, ty_full, where_clause) = self.impl_generics(false);

        let variant_tys = self.variants.iter().map(|var| &var.ty);

        quote! {
            #[automatically_derived]
            impl #impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty_full #where_clause
            {
                fn mark() {
                    #( <#variant_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )*
                }
            }
        }
    }

    /// Returns generated code implementing [`GraphQLType`] trait for this
    /// [GraphQL union][1].
    ///
    /// [`GraphQLType`]: juniper::GraphQLType
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    #[must_use]
    fn impl_graphql_type_tokens(&self) -> TokenStream {
        let scalar = &self.scalar;

        let (impl_generics, ty_full, where_clause) = self.impl_generics(false);

        let name = &self.name;
        let description = &self.description;

        let variant_tys = self.variants.iter().map(|var| &var.ty);

        quote! {
            #[automatically_derived]
            impl #impl_generics ::juniper::GraphQLType<#scalar> for #ty_full #where_clause
            {
                fn name(_ : &Self::TypeInfo) -> Option<&'static str> {
                    Some(#name)
                }

                fn meta<'r>(
                    info: &Self::TypeInfo,
                    registry: &mut ::juniper::Registry<'r, #scalar>
                ) -> ::juniper::meta::MetaType<'r, #scalar>
                where #scalar: 'r,
                {
                    let types = [
                        #( registry.get_type::<#variant_tys>(info), )*
                    ];
                    registry.build_union_type::<#ty_full>(info, &types)
                        #description
                        .into_meta()
                }
            }
        }
    }

    /// Returns generated code implementing [`GraphQLValue`] trait for this
    /// [GraphQL union][1].
    ///
    /// [`GraphQLValue`]: juniper::GraphQLValue
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    #[must_use]
    fn impl_graphql_value_tokens(&self) -> TokenStream {
        let scalar = &self.scalar;
        let context = &self.context;

        let (impl_generics, ty_full, where_clause) = self.impl_generics(false);

        let name = &self.name;

        let match_variant_names = self
            .variants
            .iter()
            .map(|v| v.method_concrete_type_name_tokens(scalar));

        let variant_resolvers = self
            .variants
            .iter()
            .map(|v| v.method_resolve_into_type_tokens(scalar));

        quote! {
            #[automatically_derived]
            impl #impl_generics ::juniper::GraphQLValue<#scalar> for #ty_full #where_clause
            {
                type Context = #context;
                type TypeInfo = ();

                fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> {
                    <Self as ::juniper::GraphQLType<#scalar>>::name(info)
                }

                fn concrete_type_name(
                    &self,
                    context: &Self::Context,
                    info: &Self::TypeInfo,
                ) -> String {
                    #( #match_variant_names )*
                    ::std::panic!(
                        "GraphQL union `{}` cannot be resolved into any of its \
                         variants in its current state",
                        #name,
                    );
                }

                fn resolve_into_type(
                    &self,
                    info: &Self::TypeInfo,
                    type_name: &str,
                    _: Option<&[::juniper::Selection<'_, #scalar>]>,
                    executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>,
                ) -> ::juniper::ExecutionResult<#scalar> {
                    let context = executor.context();
                    #( #variant_resolvers )*
                    return Err(::juniper::FieldError::from(::std::format!(
                        "Concrete type `{}` is not handled by instance \
                         resolvers on GraphQL union `{}`",
                        type_name, #name,
                    )));
                }
            }
        }
    }

    /// Returns generated code implementing [`GraphQLValueAsync`] trait for this
    /// [GraphQL union][1].
    ///
    /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    #[must_use]
    fn impl_graphql_value_async_tokens(&self) -> TokenStream {
        let scalar = &self.scalar;

        let (impl_generics, ty_full, where_clause) = self.impl_generics(true);

        let name = &self.name;

        let variant_async_resolvers = self
            .variants
            .iter()
            .map(|v| v.method_resolve_into_type_async_tokens(scalar));

        quote! {
            #[allow(non_snake_case)]
            #[automatically_derived]
            impl #impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty_full #where_clause
            {
                fn resolve_into_type_async<'b>(
                    &'b self,
                    info: &'b Self::TypeInfo,
                    type_name: &str,
                    _: Option<&'b [::juniper::Selection<'b, #scalar>]>,
                    executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar>
                ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> {
                    let context = executor.context();
                    #( #variant_async_resolvers )*
                    return ::juniper::macros::helper::err_fut(::std::format!(
                        "Concrete type `{}` is not handled by instance \
                         resolvers on GraphQL union `{}`",
                        type_name, #name,
                    ));
                }
            }
        }
    }

    /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and
    /// [`WrappedType`] traits for this [GraphQL union][1].
    ///
    /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes
    /// [`BaseType`]: juniper::macros::reflect::BaseType
    /// [`WrappedType`]: juniper::macros::reflect::WrappedType
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    #[must_use]
    pub(crate) fn impl_reflection_traits_tokens(&self) -> TokenStream {
        let scalar = &self.scalar;
        let name = &self.name;
        let variants = self.variants.iter().map(|var| &var.ty);
        let (impl_generics, ty, where_clause) = self.impl_generics(false);

        quote! {
            #[automatically_derived]
            impl #impl_generics ::juniper::macros::reflect::BaseType<#scalar>
                for #ty
                #where_clause
            {
                const NAME: ::juniper::macros::reflect::Type = #name;
            }

            #[automatically_derived]
            impl #impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar>
                for #ty
                #where_clause
            {
                const NAMES: ::juniper::macros::reflect::Types = &[
                    <Self as ::juniper::macros::reflect::BaseType<#scalar>>::NAME,
                    #(<#variants as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),*
                ];
            }

            #[automatically_derived]
            impl #impl_generics ::juniper::macros::reflect::WrappedType<#scalar>
                for #ty
                #where_clause
            {
                const VALUE: ::juniper::macros::reflect::WrappedValue = 1;
            }
        }
    }
}

/// Definition of [GraphQL union][1] variant for code generation.
///
/// [1]: https://spec.graphql.org/October2021#sec-Unions
struct VariantDefinition {
    /// Rust type that this [GraphQL union][1] variant resolves into.
    ///
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    ty: syn::Type,

    /// Rust code for value resolution of this [GraphQL union][1] variant.
    ///
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    resolver_code: syn::Expr,

    /// Rust code for checking whether [GraphQL union][1] should be resolved
    /// into this variant.
    ///
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    resolver_check: syn::Expr,

    /// Rust type of [`Context`] that this [GraphQL union][1] variant requires
    /// for resolution.
    ///
    /// It's available only when code generation happens for Rust traits and a
    /// trait method contains context argument.
    ///
    /// [`Context`]: juniper::Context
    /// [1]: https://spec.graphql.org/October2021#sec-Unions
    context: Option<syn::Type>,
}

impl VariantDefinition {
    /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0]
    /// method, which returns name of the underlying GraphQL type contained in
    /// this [`VariantDefinition`].
    ///
    /// [0]: juniper::GraphQLValue::concrete_type_name
    #[must_use]
    fn method_concrete_type_name_tokens(&self, scalar: &scalar::Type) -> TokenStream {
        let ty = &self.ty;
        let check = &self.resolver_check;

        quote! {
            if #check {
                return <#ty as ::juniper::GraphQLType<#scalar>>::name(info)
                    .unwrap()
                    .to_string();
            }
        }
    }

    /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0]
    /// method, which resolves the underlying GraphQL type contained in this
    /// [`VariantDefinition`] synchronously.
    ///
    /// [0]: juniper::GraphQLValue::resolve_into_type
    #[must_use]
    fn method_resolve_into_type_tokens(&self, scalar: &scalar::Type) -> TokenStream {
        let ty = &self.ty;
        let ty_name = ty.to_token_stream().to_string();
        let expr = &self.resolver_code;
        let resolving_code = gen::sync_resolving_code();

        quote! {
            if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info)
                .ok_or_else(|| ::juniper::macros::helper::err_unnamed_type(#ty_name))?
            {
                let res = { #expr };
                return #resolving_code;
            }
        }
    }

    /// Returns generated code for the
    /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which
    /// resolves the underlying GraphQL type contained in this
    /// [`VariantDefinition`] asynchronously.
    ///
    /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async
    #[must_use]
    fn method_resolve_into_type_async_tokens(&self, scalar: &scalar::Type) -> TokenStream {
        let ty = &self.ty;
        let ty_name = ty.to_token_stream().to_string();
        let expr = &self.resolver_code;
        let resolving_code = gen::async_resolving_code(None);

        quote! {
            match <#ty as ::juniper::GraphQLType<#scalar>>::name(info) {
                Some(name) => {
                    if type_name == name {
                        let fut = ::juniper::futures::future::ready({ #expr });
                        return #resolving_code;
                    }
                }
                None => return ::juniper::macros::helper::err_unnamed_type_fut(#ty_name),
            }
        }
    }
}

/// Emerges [`Attr::external_resolvers`] into the given [GraphQL union][1]
/// `variants`.
///
/// If duplication happens, then resolving code is overwritten with the one from
/// `external_resolvers`.
///
/// [1]: https://spec.graphql.org/October2021#sec-Unions
fn emerge_union_variants_from_attr(
    variants: &mut Vec<VariantDefinition>,
    external_resolvers: AttrResolvers,
) {
    if external_resolvers.is_empty() {
        return;
    }

    for (ty, rslvr) in external_resolvers {
        let resolver_fn = rslvr.into_inner();
        let resolver_code = parse_quote! {
            #resolver_fn(self, ::juniper::FromContext::from(context))
        };
        // Doing this may be quite an expensive, because resolving may contain
        // some heavy computation, so we're preforming it twice. Unfortunately,
        // we have no other options here, until the `juniper::GraphQLType`
        // itself will allow to do it in some cleverer way.
        let resolver_check = parse_quote! {
            ({ #resolver_code } as ::std::option::Option<&#ty>).is_some()
        };

        if let Some(var) = variants.iter_mut().find(|v| v.ty == ty) {
            var.resolver_code = resolver_code;
            var.resolver_check = resolver_check;
        } else {
            variants.push(VariantDefinition {
                ty,
                resolver_code,
                resolver_check,
                context: None,
            })
        }
    }
}

/// Checks whether all [GraphQL union][1] `variants` represent a different Rust
/// type.
///
/// # Notice
///
/// This is not an optimal implementation, as it's possible to bypass this check
/// by using a full qualified path instead (`crate::Test` vs `Test`). Since this
/// requirement is mandatory, the static assertion [`assert_type_ne_all!`][2] is
/// used to enforce this requirement in the generated code. However, due to the
/// bad error message this implementation should stay and provide guidance.
///
/// [1]: https://spec.graphql.org/October2021#sec-Unions
/// [2]: juniper::sa::assert_type_ne_all
fn all_variants_different(variants: &[VariantDefinition]) -> bool {
    let mut types: Vec<_> = variants.iter().map(|var| &var.ty).collect();
    types.dedup();
    types.len() == variants.len()
}


text:
layout: default
title: save an image from a canvas
tags:
- web
- guide
group: web

---

# How to save an image to disk from a canvas

The Canvas object includes a `toDataURL` method that returns a dataURI (a really *really* long URI that contains a base64-encoded version of the image)

    canvas = $('<canvas>');
    ...

    superLongURI = canvas.toDataURL("image/png");

This is super handy if you want to to generate images from javascript.

I've never had trouble using these images-encoded-within-urls within javascript, for example within an `img` tag's `src` attribute.

The problem comes if you want to link to the image, or if the user tries to save it: Google Chrome's address bar can't handle URLs longer than 2 megabytes.

The solution is to convert the super-long URI to a blob, and link to that blob's URI rather than the really-long dataURI.

## usage

    canvas = $('<canvas>');
    ...

    superLongURI = canvas.toDataURL("image/png");

    // to save the image to the user's computer with a specified filename:
    ImageSaver.download_data_uri(superLongURI, fileName);

    // to only generate a shortened URI, not saving it.
    shortURI = ImageSaver.make_url_from_data(dataURI);

## javascript

The following javascirpt object provides three functions:

*   `ImageSaver.download_data_uri(dataURI, fileName)` main wrapper function to download an image, requires both functions below
*   `ImageSaver.make_url_from_data(dataURI)` makes a blob from a dataURI and returns that blob's URI, requires make_blob
*   `ImageSaver.make_blob(dataURI)` makes a blob from a dataURI and returns that blob.

### the script

    ImageSaver = {
        // function to force-download from a data uri as a filename
        // nb the download="filename" attribute isn't yet supported by safari
        download_data_uri: function(dataURI, fileName) {
            var tempUrl = ImageSaver.make_url_from_data(dataURI);
            var link = $('<a href="' + tempUrl + '" id="download" download="' + fileName + '" target="_blank"> </a>');
            $("body").append(link);
            $("#download").get(0).click();
        },

        // function to generate a temporary browser index url for a datauri
        // if a data-uri is larger than 2mb, chrome's address bar can't handle it.
        // fortunately, you can blob it and then use a temporary blob url
        make_url_from_data: function(dataURI) {
            var blob = ImageSaver.make_blob(dataURI);
            var tempUrl = URL.createObjectURL(blob);
            return tempUrl;
        },

        // function to convert a datauri to a blob
        // Blobs are temporary data structures that can hold binary data, and make that data accessible through a short url. They can probably do other things too; I have no idea.
        make_blob: function(dataURI) {
            // convert base64 to raw binary data held in a string
            // doesn't handle URLEncoded DataURIs
            var byteString;
            if (dataURI.split(',')[0].indexOf('base64') >= 0)
                byteString = atob(dataURI.split(',')[1]);
            else
                byteString = unescape(dataURI.split(',')[1]);
            // separate out the mime component
            var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

            // write the bytes of the string to an ArrayBuffer
            var ab = new ArrayBuffer(byteString.length);
            var ia = new Uint8Array(ab);
            for (var i = 0; i < byteString.length; i++) {
                ia[i] = byteString.charCodeAt(i);
            };

            // write the ArrayBuffer to a blob, and you're done
            return new Blob([ab], {
                type: mimeString
            });
        }
    }

redpajama_set_name: RedPajamaGithub


text:
'use strict';

const capitalizeFirstLetter = require('./capitalizeFirstLetter');
const chunkString = require('./chunkString');
const getSender = require('./getSender');
const isDefined = require('./isDefined');
const randomItem = require('./randomItem');
const splitResponse = require('./splitResponse');

module.exports = {
  capitalizeFirstLetter,
  chunkString,
  getSender,
  isDefined,
  randomItem,
  splitResponse
};


text:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JMessageClient (android API)</title>
<meta name="date" content="2018-05-08">
<link rel="stylesheet" type="text/css" href="../../../../../stylesheet.css" title="Style">

JavaScript is disabled on your browser.

Skip navigation links

Overview

Package

Class

Tree

Deprecated

Index

Help

Prev Class

Next Class

Frames

No Frames

All Classes

Summary:

Nested |

Field |

Constr |

Method

Detail:

Field |

Constr |

Method

cn.jpush.im.android.api

Class JMessageClient

java.lang.Object

cn.jpush.im.android.api.JMessageClient

public class JMessageClient
extends java.lang.Object

极光IM SDK 的主入口类,提供大部分的接口调用。
 Main Entrance for JMessage IM SDK.

Field Summary

Fields

Modifier and Type
Field and Description

static int
FLAG_NOTIFY_DEFAULT

展示通知栏通知,所有设置均默认打开。

static int
FLAG_NOTIFY_DISABLE

不展示通知栏通知

static int
FLAG_NOTIFY_SILENCE

展示通知栏通知,其他设置均为关闭。

static int
FLAG_NOTIFY_WITH_LED

收到通知时,点亮呼吸灯

static int
FLAG_NOTIFY_WITH_SOUND

收到通知时,发出声音

static int
FLAG_NOTIFY_WITH_VIBRATE

收到通知时,产生震动

static int
NOTI_MODE_DEFAULT

Deprecated.

deprecated in 2.2.0,use setNotificationFlag(int) instead.

static int
NOTI_MODE_NO_NOTIFICATION

Deprecated.

deprecated in 2.2.0,use setNotificationFlag(int) instead.

static int
NOTI_MODE_NO_SOUND

Deprecated.

deprecated in 2.2.0,use setNotificationFlag(int) instead.

static int
NOTI_MODE_NO_VIBRATE

Deprecated.

deprecated in 2.2.0,use setNotificationFlag(int) instead.

static int
NOTI_MODE_SILENCE

Deprecated.

deprecated in 2.2.0,use setNotificationFlag(int) instead.

Constructor Summary

Constructors

Constructor and Description

JMessageClient()

Method Summary

All Methods Static Methods Concrete Methods Deprecated Methods

Modifier and Type
Method and Description

static void
addGroupMembers(long groupID,
               java.util.List<java.lang.String> userNameList,
               BasicCallback callback)

向群组中添加成员。本方法所有传入的username,会默认在本应用下查找,
 若要跨应用添加其他用户请使用addGroupMembers(long, String, List, BasicCallback)

static void
addGroupMembers(long groupID,
               java.lang.String appKey,
               java.util.List<java.lang.String> userNameList,
               BasicCallback callback)

向群组中添加成员,通过指定appKey可以实现跨应用添加其他appKey下用户进群组

static void
addUsersToBlacklist(java.util.List<java.lang.String> usernames,
                   BasicCallback callback)

将用户加入黑名单,对方用户将无法给你发消息。此方法默认在本应用下查找,
 若想要跨应用添加其他appKey下用户请使用addUsersToBlacklist(List, String, BasicCallback)

static void
addUsersToBlacklist(java.util.List<java.lang.String> usernames,
                   java.lang.String appKey,
                   BasicCallback callback)

将用户添加进黑名单,对方将无法给你发消息。通过指定appKey可以实现跨应用将用户加入黑名单

static void
adminDissolveGroup(long groupID,
                  BasicCallback callback)

解散指定的群组,只有群的群主有权限解散。
 群组解散后会以message的形式通知到群内所有成员,类型为EventNotificationContent.EventNotificationType.group_dissolved

static void
applyJoinGroup(long groupID,
              java.lang.String reason,
              BasicCallback callback)

申请加入群组,只有公开群组(公开群组定义参考createPublicGroup(String, String, CreateGroupCallback)),
 才能发送入群申请

static Message
createAtGroupMembersMessage(long groupID,
                           java.util.List<UserInfo> atList,
                           MessageContent content)

创建一条@群组中user的消息,此方法是创建@群成员message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法;
 如果想要关注会话实例或需要自定义fromName的开发者请通过Conversation.createSendMessage(MessageContent, List, String)
 接口来创建消息.

static void
createGroup(java.lang.String groupName,
           java.lang.String groupDesc,
           CreateGroupCallback callback)

创建群组,群组创建成功后,创建者会默认包含在群成员中。该群组默认为私有群组,只能群内成员邀请他人入群,不支持申请入群。
 如果想创建公开群组使用createPublicGroup(String, String, CreateGroupCallback)
 通过GroupBasicInfo.getGroupType()得到群组类型

static void
createGroup(java.lang.String groupName,
           java.lang.String groupDesc,
           java.io.File groupAvatarFile,
           java.lang.String format,
           CreateGroupCallback callback)

创建群组,群组创建成功后,创建者会默认包含在群成员中。该群组默认为私有群组,只能群内成员邀请他人入群,不支持申请入群。
 如果想创建公开群组使用createPublicGroup(String, String, CreateGroupCallback)
 使用此接口创建群组时可以指定群头像,并且可以指定头像文件在后台存储时的扩展名,如果填空或者不填,则后台存储文件时将没有扩展名。
 通过GroupBasicInfo.getGroupType()得到群组类型

static Message
createGroupCustomMessage(long groupID,
                        java.util.Map<? extends java.lang.String,? extends java.lang.String> valuesMap)

创建一条群聊自定义消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createGroupFileMessage(long groupID,
                      java.io.File file,
                      java.lang.String fileName)

创建一条群聊file消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createGroupImageMessage(long groupID,
                       java.io.File imageFile)

创建一条群聊图片信息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createGroupLocationMessage(long groupId,
                          double latitude,
                          double longitude,
                          int scale,
                          java.lang.String address)

创建一条群聊地理位置消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createGroupTextMessage(long groupID,
                      java.lang.String text)

创建一条群聊文本信息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createGroupVideoMessage(long groupID,
                       android.graphics.Bitmap thumbImage,
                       java.lang.String thumbFormat,
                       java.io.File videoFile,
                       java.lang.String videoFileName,
                       int duration)

创建一条群聊video消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createGroupVoiceMessage(long groupID,
                       java.io.File voiceFile,
                       int duration)

创建一条群聊语音信息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static void
createPublicGroup(java.lang.String groupName,
                 java.lang.String groupDesc,
                 CreateGroupCallback callback)

创建公开群组, 群组创建成功后,创建者会默认包含在群成员中。
 公开群组与私有群组的区别是公开群组只有群主和管理员邀请入群才能直接入群,群内其他人员邀请他人入群需要经过群主或管理员审批,
 管理员相关见GroupInfo.addGroupKeeper(List, BasicCallback), 同时公开群组支持申请入群操作applyJoinGroup(long, String, BasicCallback),
 申请入群时也需要群主或管理员审批, 需要审批入群时群主和管理员会收到GroupApprovalEvent事件
 通过GroupBasicInfo.getGroupType()得到群组类型

static void
createPublicGroup(java.lang.String groupName,
                 java.lang.String groupDesc,
                 java.io.File groupAvatarFile,
                 java.lang.String format,
                 CreateGroupCallback callback)

创建公开群组,群组创建成功后,创建者会默认包含在群成员中。
 公开群组定义参考createPublicGroup(String, String, CreateGroupCallback)
 使用此接口创建群组时可以指定群头像,并且可以指定头像文件在后台存储时的扩展名,如果填空或者不填,则后台存储文件时将没有扩展名。
 通过GroupBasicInfo.getGroupType()得到群组类型

static Message
createSingleCustomMessage(java.lang.String username,
                         java.util.Map<? extends java.lang.String,? extends java.lang.String> valuesMap)

创建一条单聊自定义消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createSingleCustomMessage(java.lang.String username,
                         java.lang.String appKey,
                         java.util.Map<? extends java.lang.String,? extends java.lang.String> valuesMap)

创建一条单聊自定义消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createSingleFileMessage(java.lang.String userName,
                       java.lang.String appKey,
                       java.io.File file,
                       java.lang.String fileName)

创建一条单聊file消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createSingleImageMessage(java.lang.String username,
                        java.io.File imageFile)

创建一条单聊图片信息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createSingleImageMessage(java.lang.String username,
                        java.lang.String appKey,
                        java.io.File imageFile)

创建一条单聊图片信息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createSingleLocationMessage(java.lang.String username,
                           java.lang.String appKey,
                           double latitude,
                           double longitude,
                           int scale,
                           java.lang.String address)

创建一条单聊地理位置消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createSingleTextMessage(java.lang.String username,
                       java.lang.String text)

创建一条单聊文本消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createSingleTextMessage(java.lang.String username,
                       java.lang.String appKey,
                       java.lang.String text)

创建一条单聊文本消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createSingleVideoMessage(java.lang.String userName,
                        java.lang.String appKey,
                        android.graphics.Bitmap thumbImage,
                        java.lang.String thumbFormat,
                        java.io.File videoFile,
                        java.lang.String videoFileName,
                        int duration)

创建一条单聊video消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createSingleVoiceMessage(java.lang.String username,
                        java.io.File voiceFile,
                        int duration)

创建一条单聊语音信息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static Message
createSingleVoiceMessage(java.lang.String username,
                        java.lang.String appKey,
                        java.io.File voiceFile,
                        int duration)

创建一条单聊语音信息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

static boolean
deleteChatRoomConversation(long roomID)

删除聊天室会话,同时删除掉本地相关缓存文件

static boolean
deleteGroupConversation(long groupID)

删除群聊的会话,同时删除掉本地聊天记录

static boolean
deleteSingleConversation(java.lang.String userName)

删除单聊的会话,同时删除掉本地聊天记录。默认删除本appkey下username的会话

static boolean
deleteSingleConversation(java.lang.String username,
                        java.lang.String appkey)

删除与指定appkey下username的单聊的会话,同时删除掉本地聊天记录。,如果appkey为空则默认尝试删除
 本应用appkey下对应username的会话。

static void
delUsersFromBlacklist(java.util.List<java.lang.String> usernames,
                     BasicCallback callback)

将用户移出黑名单。本方法所有传入的username,默认移除的是本应用下用户,
 若想要跨应用移除其他appKey下的用户请使用delUsersFromBlacklist(List, String, BasicCallback)

static void
delUsersFromBlacklist(java.util.List<java.lang.String> usernames,
                     java.lang.String appKey,
                     BasicCallback callback)

将用户移出黑名单,通过指定appKey可以实现跨应用将用户移出黑名单

static void
enterGroupConversation(long groupID)

在进入聊天会话界面时调用,设置当前正在聊天的对象,sdk用来判断收到消息时的notification是否需要展示。
 调用此接口之后,收到对应群组中发来的消息时,sdk不会弹出通知栏提示。

static void
enterSingleConversaion(java.lang.String username)

Deprecated.

deprecated in 1.2.0 useenterSingleConversation(String, String)
 instead.

static void
enterSingleConversation(java.lang.String username)

在进入聊天会话界面时调用,设置当前正在聊天的对象,sdk用来判断notification是否需要展示,默认进入的
 是本应用appkey下用户的会话。此接口传入的数据采用覆盖逻辑,后面传入的参数会覆盖掉之前的设置。

static void
enterSingleConversation(java.lang.String username,
                       java.lang.String appkey)

在进入聊天会话界面时调用,设置当前正在聊天的对象,sdk用来判断收到消息时的notification是否需要展示。
 调用此接口之后,收到对应username发来的消息时,sdk不会弹出通知栏提示。

static void
exitConversaion()

Deprecated.

deprecated in 1.2.0. use exitConversation() instead.

static void
exitConversation()

推荐在退出聊天会话界面时调用,清除当前正在聊天的对象,用于判断notification是否需要展示

static void
exitGroup(long groupId,
         BasicCallback callback)

退出群组,如果是群主退群,则相关群主权利移交给下一位群成员。如果此时群组中没有其他人,
 则群组将会被解散。

static void
forwardMessage(Message message,
              Conversation conv,
              MessageSendingOptions options,
              BasicCallback callback)

Deprecated.

deprecated since jmessage 2.6.0 use forwardMessage(Message, Conversation, MessageSendingOptions, RequestCallback) instead.

static void
forwardMessage(Message message,
              Conversation conv,
              MessageSendingOptions options,
              RequestCallback<Message> callback)

转发消息,注意只有发送成功或收到的消息才可转发,符合转发要求后会创建新的消息发送,
 创建的消息会在回调中返回(无论发送是否成功),如果不符合转发要求则不会创建新消息 message返回null。

static int
getAllUnReadMsgCount()

获取未读消息总数

static void
getBlacklist(GetBlacklistCallback callback)

获取被当前用户加入黑名单的用户列表

static void
getBlockedGroupsList(GetGroupInfoListCallback callback)

获取当前用户设置的屏蔽群组列表

static Conversation
getChatRoomConversation(long roomID)

获取聊天室会话信息

static java.util.List<Conversation>
getChatRoomConversationList()

从本地数据库中获取包含所有的聊天室会话的列表

static java.util.List<Conversation>
getConversationList()

从本地数据库中获取会话列表,默认按照会话的最后一条消息的时间,降序排列

static java.util.List<Conversation>
getConversationListByDefault()

从本地数据库中获取会话列表,默认不排序。

static Conversation
getGroupConversation(long groupID)

获取群组会话信息

static void
getGroupIDList(GetGroupIDListCallback callback)

从服务器获取当前用户所加入的群组的groupID的list,同时会将数据保存到本地数据库

static void
getGroupInfo(long groupID,
            GetGroupInfoCallback callback)

从服务器获取指定群的基本信息。

static void
getGroupMembers(long groupID,
               GetGroupMembersCallback callback)

从本地获取群成员username list,如果本地不存在则从服务器获取。

static UserInfo
getMyInfo()

获取当前的用户信息

static void
getNoDisturbGlobal(IntegerCallback callback)

获取全局免打扰标识,回调结果中,1表示已设置,其他表示未设置。

static void
getNoDisturblist(GetNoDisurbListCallback callback)

获取当前用户设置的免打扰名单列表

static int
getNotificationFlag()

获取当前通知栏的展示类型。

static void
getPublicGroupListByApp(java.lang.String appKey,
                       int start,
                       int count,
                       RequestCallback<java.util.List<GroupBasicInfo>> callback)

获取指定应用appKey所属下公开群组基本信息, 如果appKey为空则默认使用本应用appKey.

static java.lang.String
getSdkVersionString()

获取sdk版本号

static Conversation
getSingleConversation(java.lang.String username)

获取单聊会话信息,默认获取本appkey下username的单聊会话。

static Conversation
getSingleConversation(java.lang.String username,
                     java.lang.String appkey)

获取与指定appkey下username的单聊会话信息,如果appkey为空则默认取本应用appkey下对应username的会话。

static void
getUserInfo(java.lang.String username,
           GetUserInfoCallback callback)

获取用户信息。默认获取当前应用appkey下的用户信息。

static void
getUserInfo(java.lang.String username,
           java.lang.String appkey,
           GetUserInfoCallback callback)

获取用户信息,此接口可用来获取不同appkey下用户的信息,如果appkey为空,则默认获取当前appkey下的用户信息。

static void
init(android.content.Context context)

SDK初始化,默认不启用消息记录漫游。

static void
init(android.content.Context context,
    boolean msgRoaming)

SDK初始化,同时指定是否启用消息记录漫游,如果启用,sdk将会把当前登陆用户的消息历史记录同步到本地。

static boolean
isCurrentUserPasswordValid(java.lang.String password)

判断输入的字符串是否与当前用户的密码匹配

static void
login(java.lang.String userName,
     java.lang.String password,
     BasicCallback callback)

用户登录

static void
login(java.lang.String userName,
     java.lang.String password,
     RequestCallback<java.util.List<DeviceInfo>> callback)

用户登陆,并且在回调中获取用户账号所登陆过的设备信息DeviceInfo

 每个端:移动端(Android 、iOS),PC端,Web端(JS、微信小程序)只保存最近一次设备登陆记录。

static void
logout()

用户登出接口,调用后用户将无法收到消息。登出动作必定成功,开发者可以不需要关心结果回调。

static void
register(java.lang.String userName,
        java.lang.String password,
        BasicCallback callback)

用户注册

static void
register(java.lang.String userName,
        java.lang.String password,
        RegisterOptionalUserInfo optionalUserInfo,
        BasicCallback callback)

用户注册

static void
registerEventReceiver(java.lang.Object receiver)

注册事件接收者,具体用法参考官方文档事件处理
 一节

static void
registerEventReceiver(java.lang.Object receiver,
                     int priority)

注册事件接收者,具体用法参考官方文档事件处理
 一节

static void
removeGroupMembers(long groupID,
                  java.util.List<java.lang.String> userNameList,
                  BasicCallback callback)

踢出群组成员。本方法所有传入的username,都默认在本应用下查找,
 若要跨应用踢出其他appKey下的群成员请使用removeGroupMembers(long, String, List, BasicCallback)

static void
removeGroupMembers(long groupID,
                  java.lang.String appKey,
                  java.util.List<java.lang.String> userNameList,
                  BasicCallback callback)

踢出群组中成员,通过指定appKey可以实现跨应用踢出群组成员

static void
sendCrossDeviceTransCommand(PlatformType platformType,
                           java.lang.String msg,
                           BasicCallback callback)

发送透传消息给当前用户在其他平台已登录的设备。
 消息不会进入到后台的离线存储中去,仅当对方用户当前在线时,透传消息才会成功送达。
 透传命令送达时,接收方会收到一个CommandNotificationEvent事件通知。
 sdk不会将此类透传消息内容本地化。

static void
sendGroupTransCommand(long gid,
                     java.lang.String msg,
                     BasicCallback callback)

发送消息透传给群。
 消息不会进入到后台的离线存储中去,仅当对方用户当前在线时,透传消息才会成功送达。
 透传命令送达时,接收方会收到一个CommandNotificationEvent事件通知。
 sdk不会将此类透传消息内容本地化。

static void
sendMessage(Message message)

发送消息,使用默认发送配置参数

static void
sendMessage(Message message,
           MessageSendingOptions options)

发送消息.并且可以使用MessageSendingOptions对发送的一些发送参数做配置。
 注意这些配置仅对本次消息发送生效。

static void
sendSingleTransCommand(java.lang.String username,
                      java.lang.String appKey,
                      java.lang.String msg,
                      BasicCallback callback)

发送消息透传给个人。
 消息不会进入到后台的离线存储中去,仅当对方用户当前在线时,透传消息才会成功送达。
 透传命令送达时,接收方会收到一个CommandNotificationEvent事件通知。
 sdk不会将此类透传消息内容本地化。

static void
setDebugMode(boolean isDebugMode)

设置debug模式,sdk将会输出更多debug信息。仅在开发阶段使用,应用对外发布时应关闭。

static void
setNoDisturbGlobal(int noDisturbGlobal,
                  BasicCallback callback)

设置全局免打扰标识,设置之后用户在所有设备上收到消息时通知栏都不会弹出消息通知。

static void
setNotificationFlag(int flag)

设置通知的展示类型,默认所有设置都会打开

static void
setNotificationMode(int mode)

Deprecated.

deprecated in 2.2.0 use setNotificationFlag(int) instead

static void
unRegisterEventReceiver(java.lang.Object receiver)

解绑事件接收者,解绑后接收者将不再接收到事件

static void
updateGroupDescription(long groupID,
                      java.lang.String groupDesc,
                      BasicCallback callback)

Deprecated.

deprecated in 2.3.0,use GroupInfo.updateDescription(String, BasicCallback) instead

static void
updateGroupName(long groupID,
               java.lang.String groupName,
               BasicCallback callback)

Deprecated.

deprecated in 2.3.0,use GroupInfo.updateName(String, BasicCallback) instead

static void
updateMyInfo(UserInfo.Field updateField,
            UserInfo userInfo,
            BasicCallback callback)

更新当前登录的用户的用户信息

static void
updateUserAvatar(java.io.File avatar,
                BasicCallback callback)

更新用户头像,建议用户在上传头像前先对头像先进行压缩,否则在调用UserInfo.getBigAvatarBitmap(GetAvatarBitmapCallback)
 接口拿头像的原图时,有可能会抛出OOM异常。

static void
updateUserAvatar(java.io.File avatar,
                java.lang.String format,
                BasicCallback callback)

更新用户头像,建议用户在上传头像前先对头像先进行压缩,否则在调用UserInfo.getBigAvatarBitmap(GetAvatarBitmapCallback)
 接口拿头像的原图时,有可能会抛出OOM异常。

static void
updateUserPassword(java.lang.String oldPassword,
                  java.lang.String newPassword,
                  BasicCallback callback)

更新密码

Methods inherited from class java.lang.Object

equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Field Detail

NOTI_MODE_NO_NOTIFICATION

public static final int NOTI_MODE_NO_NOTIFICATION

Deprecated. deprecated in 2.2.0,use setNotificationFlag(int) instead.

不展示通知

See Also:

Constant Field Values

NOTI_MODE_DEFAULT

public static final int NOTI_MODE_DEFAULT

Deprecated. deprecated in 2.2.0,use setNotificationFlag(int) instead.

展示通知,有声音有震动

See Also:

Constant Field Values

NOTI_MODE_NO_SOUND

public static final int NOTI_MODE_NO_SOUND

Deprecated. deprecated in 2.2.0,use setNotificationFlag(int) instead.

展示通知,无声音有震动

See Also:

Constant Field Values

NOTI_MODE_NO_VIBRATE

public static final int NOTI_MODE_NO_VIBRATE

Deprecated. deprecated in 2.2.0,use setNotificationFlag(int) instead.

展示通知,有声音无震动

See Also:

Constant Field Values

NOTI_MODE_SILENCE

public static final int NOTI_MODE_SILENCE

Deprecated. deprecated in 2.2.0,use setNotificationFlag(int) instead.

展示通知,无声音无震动

See Also:

Constant Field Values

FLAG_NOTIFY_DEFAULT

public static final int FLAG_NOTIFY_DEFAULT

展示通知栏通知,所有设置均默认打开。

 此为通知栏的默认行为

Since:

2.2.0

See Also:

Constant Field Values

FLAG_NOTIFY_SILENCE

public static final int FLAG_NOTIFY_SILENCE

展示通知栏通知,其他设置均为关闭。

Since:

2.2.0

See Also:

Constant Field Values

FLAG_NOTIFY_DISABLE

public static final int FLAG_NOTIFY_DISABLE

不展示通知栏通知

Since:

2.2.0

See Also:

Constant Field Values

FLAG_NOTIFY_WITH_SOUND

public static final int FLAG_NOTIFY_WITH_SOUND

收到通知时,发出声音

Since:

2.2.0

See Also:

Constant Field Values

FLAG_NOTIFY_WITH_VIBRATE

public static final int FLAG_NOTIFY_WITH_VIBRATE

收到通知时,产生震动

Since:

2.2.0

See Also:

Constant Field Values

FLAG_NOTIFY_WITH_LED

public static final int FLAG_NOTIFY_WITH_LED

收到通知时,点亮呼吸灯

Since:

2.2.0

See Also:

Constant Field Values

Constructor Detail

JMessageClient

public JMessageClient()

Method Detail

init

public static void init(android.content.Context context)

SDK初始化,默认不启用消息记录漫游。

 在调用IM其他接口前必须先调此接口初始化SDK,推荐在application类中调用。

Parameters:

context - 应用程序上下文对象

init

public static void init(android.content.Context context,
                        boolean msgRoaming)

SDK初始化,同时指定是否启用消息记录漫游,如果启用,sdk将会把当前登陆用户的消息历史记录同步到本地。

 在调用IM其他接口前必须先调此接口初始化SDK,推荐在application类中调用。

Parameters:

context - 应用程序上下文对象

msgRoaming - 是否需要消息记录漫游。 true-需要,false-不需要

Since:

2.1.0

setDebugMode

public static void setDebugMode(boolean isDebugMode)

设置debug模式,sdk将会输出更多debug信息。仅在开发阶段使用,应用对外发布时应关闭。

Parameters:

isDebugMode - true-打开debug模式, false-关闭debug模式。

register

public static void register(java.lang.String userName,
                            java.lang.String password,
                            BasicCallback callback)

用户注册

Parameters:

userName - 开发者注册的用户标识,应该唯一。

password - 用户登录密码,推荐将字符串加密。

callback - 回调接口

register

public static void register(java.lang.String userName,
                            java.lang.String password,
                            RegisterOptionalUserInfo optionalUserInfo,
                            BasicCallback callback)

用户注册

Parameters:

userName - 开发者注册的用户标识,应该唯一。

password - 用户登录密码,推荐将字符串加密。

optionalUserInfo - 注册时的用户其他信息

callback - 回调接口

Since:

2.3.0

login

public static void login(java.lang.String userName,
                         java.lang.String password,
                         BasicCallback callback)

用户登录

Parameters:

userName - 开发者注册的用户名,应该唯一。

password - 用户登录密码,推荐将字符串加密。

callback - 回调接口

login

public static void login(java.lang.String userName,
                         java.lang.String password,
                         RequestCallback<java.util.List<DeviceInfo>> callback)

用户登陆,并且在回调中获取用户账号所登陆过的设备信息DeviceInfo

 每个端:移动端(Android 、iOS),PC端,Web端(JS、微信小程序)只保存最近一次设备登陆记录。

Parameters:

userName - 开发者注册的用户名,应该唯一。

password - 用户登录密码,推荐将字符串加密。

callback - 回调接口

Since:

2.5.0

getUserInfo

public static void getUserInfo(java.lang.String username,
                               GetUserInfoCallback callback)

获取用户信息。默认获取当前应用appkey下的用户信息。

Parameters:

username - 开发者注册的用户名。

callback - 回调接口

getUserInfo

public static void getUserInfo(java.lang.String username,
                               java.lang.String appkey,
                               GetUserInfoCallback callback)

获取用户信息,此接口可用来获取不同appkey下用户的信息,如果appkey为空,则默认获取当前appkey下的用户信息。

Parameters:

username - 开发者注册的用户名

appkey - 指定的appkey

callback - 回调接口

logout

public static void logout()

用户登出接口,调用后用户将无法收到消息。登出动作必定成功,开发者可以不需要关心结果回调。

isCurrentUserPasswordValid

public static boolean isCurrentUserPasswordValid(java.lang.String password)

判断输入的字符串是否与当前用户的密码匹配

Parameters:

password - 被匹配的字符串

Returns:

正确匹配返回true,其他情况返回false

updateUserPassword

public static void updateUserPassword(java.lang.String oldPassword,
                                      java.lang.String newPassword,
                                      BasicCallback callback)

更新密码

Parameters:

oldPassword - 当前用户原密码

newPassword - 当前用户新密码

callback - 回调接口

updateMyInfo

public static void updateMyInfo(UserInfo.Field updateField,
                                UserInfo userInfo,
                                BasicCallback callback)

更新当前登录的用户的用户信息

Parameters:

updateField - 需要更新的字段名

userInfo - 当前用户的UserInfo对象

callback - 回调对象

updateUserAvatar

public static void updateUserAvatar(java.io.File avatar,
                                    BasicCallback callback)

更新用户头像,建议用户在上传头像前先对头像先进行压缩,否则在调用UserInfo.getBigAvatarBitmap(GetAvatarBitmapCallback)
 接口拿头像的原图时,有可能会抛出OOM异常。

Parameters:

avatar - 头像文件

callback - 回调对象

updateUserAvatar

public static void updateUserAvatar(java.io.File avatar,
                                    java.lang.String format,
                                    BasicCallback callback)

更新用户头像,建议用户在上传头像前先对头像先进行压缩,否则在调用UserInfo.getBigAvatarBitmap(GetAvatarBitmapCallback)
 接口拿头像的原图时,有可能会抛出OOM异常。

 此接口可以指定头像文件在后台存储时的扩展名,如果填空或者不填,则后台存储文件时将没有扩展名。

Parameters:

avatar - 头像文件

format - 文件扩展名,注意名称中不要包括"."

callback - 回调对象

Since:

2.2.1

createSingleTextMessage

public static Message createSingleTextMessage(java.lang.String username,
                                              java.lang.String text)

创建一条单聊文本消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

username - 聊天对象用户名

text - 文本内容

Returns:

消息对象

createSingleTextMessage

public static Message createSingleTextMessage(java.lang.String username,
                                              java.lang.String appKey,
                                              java.lang.String text)

创建一条单聊文本消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

username - 聊天对象用户名

appKey - 聊天对象所属应用的appKey

text - 文本内容

Returns:

消息对象

createGroupTextMessage

public static Message createGroupTextMessage(long groupID,
                                             java.lang.String text)

创建一条群聊文本信息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

groupID - 群组的groupID

text - 文本内容

Returns:

消息对象

createSingleImageMessage

public static Message createSingleImageMessage(java.lang.String username,
                                               java.io.File imageFile)
                                        throws java.io.FileNotFoundException

创建一条单聊图片信息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

username - 聊天对象的用户名

imageFile - 图片文件

Returns:

消息对象

Throws:

java.io.FileNotFoundException

createSingleImageMessage

public static Message createSingleImageMessage(java.lang.String username,
                                               java.lang.String appKey,
                                               java.io.File imageFile)
                                        throws java.io.FileNotFoundException

创建一条单聊图片信息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

username - 聊天对象的用户名

appKey - 聊天对象所属应用的appKey

imageFile - 图片文件

Returns:

消息对象

Throws:

java.io.FileNotFoundException

createGroupImageMessage

public static Message createGroupImageMessage(long groupID,
                                              java.io.File imageFile)
                                       throws java.io.FileNotFoundException

创建一条群聊图片信息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

groupID - 群组的groupID

imageFile - 图片文件

Returns:

消息对象

Throws:

java.io.FileNotFoundException

createSingleVoiceMessage

public static Message createSingleVoiceMessage(java.lang.String username,
                                               java.io.File voiceFile,
                                               int duration)
                                        throws java.io.FileNotFoundException

创建一条单聊语音信息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

username - 聊天对象的用户名

voiceFile - 语音文件

duration - 语音文件时长

Returns:

消息对象

Throws:

java.io.FileNotFoundException

createSingleVoiceMessage

public static Message createSingleVoiceMessage(java.lang.String username,
                                               java.lang.String appKey,
                                               java.io.File voiceFile,
                                               int duration)
                                        throws java.io.FileNotFoundException

创建一条单聊语音信息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

username - 聊天对象的用户名

appKey - 聊天对象所属应用的appKey

voiceFile - 语音文件

duration - 语音文件时长

Returns:

消息对象

Throws:

java.io.FileNotFoundException

createGroupVoiceMessage

public static Message createGroupVoiceMessage(long groupID,
                                              java.io.File voiceFile,
                                              int duration)
                                       throws java.io.FileNotFoundException

创建一条群聊语音信息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

groupID - 群组groupID

voiceFile - 语音文件

duration - 语音文件时长

Returns:

消息对象

Throws:

java.io.FileNotFoundException

createSingleFileMessage

public static Message createSingleFileMessage(java.lang.String userName,
                                              java.lang.String appKey,
                                              java.io.File file,
                                              java.lang.String fileName)
                                       throws java.io.FileNotFoundException,
                                              JMFileSizeExceedException

创建一条单聊file消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

userName - 聊天对象的用户名

appKey - 聊天对象所属应用的appKey

file - 发送的文件

fileName - 指定发送的文件名称,如果不填或为空,则默认使用文件原名。

Returns:

消息对象

Throws:

java.io.FileNotFoundException

JMFileSizeExceedException

Since:

1.4.0

createGroupFileMessage

public static Message createGroupFileMessage(long groupID,
                                             java.io.File file,
                                             java.lang.String fileName)
                                      throws java.io.FileNotFoundException,
                                             JMFileSizeExceedException

创建一条群聊file消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

groupID - 群组groupID

file - 发送的文件

fileName - 指定发送的文件名称,如果不填或为空,则默认使用文件原名。

Returns:

消息对象

Throws:

java.io.FileNotFoundException

JMFileSizeExceedException

Since:

1.4.0

createSingleVideoMessage

public static Message createSingleVideoMessage(java.lang.String userName,
                                               java.lang.String appKey,
                                               android.graphics.Bitmap thumbImage,
                                               java.lang.String thumbFormat,
                                               java.io.File videoFile,
                                               java.lang.String videoFileName,
                                               int duration)
                                        throws java.io.IOException

创建一条单聊video消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

userName - 聊天对象用户名

appKey - 聊天对象所属应用appkey

thumbImage - 视频缩略图,可不填。

thumbFormat - 视频缩略图格式名

videoFile - 视频文件对象

videoFileName - 视频文件名称,如果不填或为空,则默认使用文件原名

duration - 视频时长

Returns:

消息对象

Throws:

java.io.IOException

Since:

2.6.0

createGroupVideoMessage

public static Message createGroupVideoMessage(long groupID,
                                              android.graphics.Bitmap thumbImage,
                                              java.lang.String thumbFormat,
                                              java.io.File videoFile,
                                              java.lang.String videoFileName,
                                              int duration)
                                       throws java.io.IOException

创建一条群聊video消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

groupID - 群组groupID

thumbImage - 视频缩略图,可不填。

thumbFormat - 视频缩略图格式名

videoFile - 视频文件对象

videoFileName - 视频文件名称,如果不填或为空,则默认使用文件原名

duration - 视频时长

Returns:

消息对象

Throws:

java.io.IOException

Since:

2.6.0

createSingleLocationMessage

public static Message createSingleLocationMessage(java.lang.String username,
                                                  java.lang.String appKey,
                                                  double latitude,
                                                  double longitude,
                                                  int scale,
                                                  java.lang.String address)

创建一条单聊地理位置消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

username - 聊天对象的用户名

appKey - 聊天对象所属应用的appKey

latitude - 纬度信息

longitude - 经度信息

scale - 地图缩放比例

address - 详细地址信息

Returns:

消息对象

Since:

1.4.0

createGroupLocationMessage

public static Message createGroupLocationMessage(long groupId,
                                                 double latitude,
                                                 double longitude,
                                                 int scale,
                                                 java.lang.String address)

创建一条群聊地理位置消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

groupId - 群组groupID

latitude - 纬度信息

longitude - 经度信息

scale - 地图缩放比例

address - 详细地址信息

Returns:

消息对象

Since:

1.4.0

createSingleCustomMessage

public static Message createSingleCustomMessage(java.lang.String username,
                                                java.util.Map<? extends java.lang.String,? extends java.lang.String> valuesMap)

创建一条单聊自定义消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

username - 聊天对象username

valuesMap - 包含自定义键值对的map.

Returns:

消息对象

createSingleCustomMessage

public static Message createSingleCustomMessage(java.lang.String username,
                                                java.lang.String appKey,
                                                java.util.Map<? extends java.lang.String,? extends java.lang.String> valuesMap)

创建一条单聊自定义消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

username - 聊天对象username

appKey - 聊天对象所属应用的appKey

valuesMap - 包含自定义键值对的map.

Returns:

消息对象

createGroupCustomMessage

public static Message createGroupCustomMessage(long groupID,
                                               java.util.Map<? extends java.lang.String,? extends java.lang.String> valuesMap)

创建一条群聊自定义消息,此方法是创建message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法
 快捷的创建一条消息。其他的情况下推荐使用Conversation.createSendMessage(MessageContent)
 接口来创建消息

Parameters:

groupID - 群组groupID

valuesMap - 包含了自定义键值对的map

Returns:

消息对象

createAtGroupMembersMessage

public static Message createAtGroupMembersMessage(long groupID,
                                                  java.util.List<UserInfo> atList,
                                                  MessageContent content)

创建一条@群组中user的消息,此方法是创建@群成员message的快捷接口,对于不需要关注会话实例的开发者可以使用此方法;
 如果想要关注会话实例或需要自定义fromName的开发者请通过Conversation.createSendMessage(MessageContent, List, String)
 接口来创建消息.

Parameters:

groupID - 群组groupID

atList - 将要@的群成员的UserInfo List

content - 创建的消息体

Returns:

消息对象

Since:

2.1.0

sendMessage

public static void sendMessage(Message message)

发送消息,使用默认发送配置参数

Parameters:

message - 消息对象

sendMessage

public static void sendMessage(Message message,
                               MessageSendingOptions options)

发送消息.并且可以使用MessageSendingOptions对发送的一些发送参数做配置。
 注意这些配置仅对本次消息发送生效。

Parameters:

message - 消息对象

options - 消息发送时的控制选项。

Since:

2.2.0

forwardMessage

public static void forwardMessage(Message message,
                                  Conversation conv,
                                  MessageSendingOptions options,
                                  BasicCallback callback)

Deprecated. deprecated since jmessage 2.6.0 use forwardMessage(Message, Conversation, MessageSendingOptions, RequestCallback) instead.

转发消息,只有发送成功或收到的消息才可转发

Parameters:

message - 需要转发的消息对象

conv - 目标会话

options - 消息转发时的控制选项,仅对此次发送生效, null则使用默认配置

callback - 回调函数

Since:

2.3.0

forwardMessage

public static void forwardMessage(Message message,
                                  Conversation conv,
                                  MessageSendingOptions options,
                                  RequestCallback<Message> callback)

转发消息,注意只有发送成功或收到的消息才可转发,符合转发要求后会创建新的消息发送,
 创建的消息会在回调中返回(无论发送是否成功),如果不符合转发要求则不会创建新消息 message返回null。

Parameters:

message - 需要转发的消息对象

conv - 目标会话

options - 消息转发时的控制选项,仅对此次发送生效, null则使用默认配置

callback - 回调函数

Since:

2.6.0

sendSingleTransCommand

public static void sendSingleTransCommand(java.lang.String username,
                                          java.lang.String appKey,
                                          java.lang.String msg,
                                          BasicCallback callback)

发送消息透传给个人。
 消息不会进入到后台的离线存储中去,仅当对方用户当前在线时,透传消息才会成功送达。
 透传命令送达时,接收方会收到一个CommandNotificationEvent事件通知。
 sdk不会将此类透传消息内容本地化。

Parameters:

username - 目标的用户名

appKey - 目标的appKey, 如果传入null或空字符串,则默认用本应用的appKey

msg - 发送的消息内容

callback - 回调函数

Since:

2.3.0

sendGroupTransCommand

public static void sendGroupTransCommand(long gid,
                                         java.lang.String msg,
                                         BasicCallback callback)

发送消息透传给群。
 消息不会进入到后台的离线存储中去,仅当对方用户当前在线时,透传消息才会成功送达。
 透传命令送达时,接收方会收到一个CommandNotificationEvent事件通知。
 sdk不会将此类透传消息内容本地化。

Parameters:

gid - 群组的gid

msg - 发送的消息内容

callback - 回调函数

Since:

2.3.0

sendCrossDeviceTransCommand

public static void sendCrossDeviceTransCommand(PlatformType platformType,
                                               java.lang.String msg,
                                               BasicCallback callback)

发送透传消息给当前用户在其他平台已登录的设备。
 消息不会进入到后台的离线存储中去,仅当对方用户当前在线时,透传消息才会成功送达。
 透传命令送达时,接收方会收到一个CommandNotificationEvent事件通知。
 sdk不会将此类透传消息内容本地化。

Parameters:

platformType - 平台类型,其中PlatformType.all表示发送给当前多端在线的其他所有设备(不包括本设备)。

msg - 发送的消息内容

callback - 回调函数

Since:

2.5.0

getConversationList

public static java.util.List<Conversation> getConversationList()

从本地数据库中获取会话列表,默认按照会话的最后一条消息的时间,降序排列

 注意此接口返回的结果中不包含聊天室会话,如果需要获取聊天室会话,请使用getChatRoomConversationList()
 或者getChatRoomConversation(long)接口获取。

Returns:

返回当前用户的会话列表,没有会话则返回空的列表,结果中不包含聊天室会话列表

getChatRoomConversationList

public static java.util.List<Conversation> getChatRoomConversationList()

从本地数据库中获取包含所有的聊天室会话的列表

Returns:

返回当前用户的聊天室会话列表,没有则返回空的列表

Since:

2.4.0

getConversationListByDefault

public static java.util.List<Conversation> getConversationListByDefault()

从本地数据库中获取会话列表,默认不排序。

 注意此接口返回的结果中不包含聊天室会话,如果需要获取聊天室会话,请使用getChatRoomConversationList()
 或者getChatRoomConversation(long)接口获取。

Returns:

返回当前用户的会话列表,没有会话则返回空的列表,结果中不包含聊天室会话列表

Since:

1.4.0

getSingleConversation

public static Conversation getSingleConversation(java.lang.String username)

获取单聊会话信息,默认获取本appkey下username的单聊会话。

Parameters:

username - 对象的userName

Returns:

返回会话对象,若不存在和指定对象的会话则返回null

getSingleConversation

public static Conversation getSingleConversation(java.lang.String username,
                                                 java.lang.String appkey)

获取与指定appkey下username的单聊会话信息,如果appkey为空则默认取本应用appkey下对应username的会话。

Parameters:

username - 用户的username

appkey - 用户所属应用的appkey

Returns:

返回会话对象,若不存在和指定对象的会话则返回null

getGroupConversation

public static Conversation getGroupConversation(long groupID)

获取群组会话信息

Parameters:

groupID - 群组的groupID

Returns:

返回会话信息,若不存在和指定对象的会话则返回null

getChatRoomConversation

public static Conversation getChatRoomConversation(long roomID)

获取聊天室会话信息

Parameters:

roomID - 群组的groupID

Returns:

返回会话信息,若不存在和指定对象的会话则返回null

Since:

2.4.0

deleteSingleConversation

public static boolean deleteSingleConversation(java.lang.String userName)

删除单聊的会话,同时删除掉本地聊天记录。默认删除本appkey下username的会话

Parameters:

userName - 用户的username

Returns:

删除成功返回true, 否则返回false

deleteSingleConversation

public static boolean deleteSingleConversation(java.lang.String username,
                                               java.lang.String appkey)

删除与指定appkey下username的单聊的会话,同时删除掉本地聊天记录。,如果appkey为空则默认尝试删除
 本应用appkey下对应username的会话。

Parameters:

username - 用户的username

appkey - 用户所属应用的appkey

Returns:

删除成功返回true, 否则返回false

deleteGroupConversation

public static boolean deleteGroupConversation(long groupID)

删除群聊的会话,同时删除掉本地聊天记录

Parameters:

groupID - 群组的groupID

Returns:

删除成功返回true, 否则返回false

deleteChatRoomConversation

public static boolean deleteChatRoomConversation(long roomID)

删除聊天室会话,同时删除掉本地相关缓存文件

Parameters:

roomID - 聊天室roomID

Returns:

删除成功返回true, 否则返回false

Since:

2.4.0

getMyInfo

public static UserInfo getMyInfo()

获取当前的用户信息

Returns:

登录成功则返回用户信息,已登出或未登录则对应用户信息为null

setNotificationMode

public static void setNotificationMode(int mode)

Deprecated. deprecated in 2.2.0 use setNotificationFlag(int) instead

设置通知的展示类型

Parameters:

mode - 通知展示类型,包括:

             NOTI_MODE_NO_NOTIFICATION 不展示通知

             NOTI_MODE_DEFAULT 展示通知,有声音有震动。

             NOTI_MODE_NO_SOUND 展示通知,无声音有震动

             NOTI_MODE_NO_VIBRATE 展示通知,有声音无震动

             NOTI_MODE_SILENCE 展示通知,无声音无震动

setNotificationFlag

public static void setNotificationFlag(int flag)

设置通知的展示类型,默认所有设置都会打开

Parameters:

flag - notification flag,包括
             FLAG_NOTIFY_WITH_SOUND,
             FLAG_NOTIFY_WITH_LED,
             FLAG_NOTIFY_WITH_VIBRATE等.
             支持 '|' 符号联结各个参数

Since:

2.2.0

getNotificationFlag

public static int getNotificationFlag()

获取当前通知栏的展示类型。

Returns:

通知栏展示类型

Since:

2.2.0

enterSingleConversaion

@Deprecated
public static void enterSingleConversaion(java.lang.String username)

Deprecated. deprecated in 1.2.0 useenterSingleConversation(String, String)
 instead.

在进入聊天会话界面时调用,设置当前正在聊天的对象,sdk用来判断notification是否需要展示,默认进入的
 是本应用appkey下用户的会话。此接口传入的数据采用覆盖逻辑,后面传入的参数会覆盖掉之前的设置。

Parameters:

username - 聊天对象的username

enterSingleConversation

public static void enterSingleConversation(java.lang.String username)

在进入聊天会话界面时调用,设置当前正在聊天的对象,sdk用来判断notification是否需要展示,默认进入的
 是本应用appkey下用户的会话。此接口传入的数据采用覆盖逻辑,后面传入的参数会覆盖掉之前的设置。

Parameters:

username - 聊天对象的username

enterSingleConversation

public static void enterSingleConversation(java.lang.String username,
                                           java.lang.String appkey)

在进入聊天会话界面时调用,设置当前正在聊天的对象,sdk用来判断收到消息时的notification是否需要展示。
 调用此接口之后,收到对应username发来的消息时,sdk不会弹出通知栏提示。

 若appkey为空则默认填充本应用的appkey。此接口传入的数据采用覆盖逻辑,后面传入的参数会覆盖掉之前的设置。

Parameters:

username - 聊天对象的username

appkey - 对象所属appkey

enterGroupConversation

public static void enterGroupConversation(long groupID)

在进入聊天会话界面时调用,设置当前正在聊天的对象,sdk用来判断收到消息时的notification是否需要展示。
 调用此接口之后,收到对应群组中发来的消息时,sdk不会弹出通知栏提示。

 此接口传入的数据采用覆盖逻辑,后面传入的参数会覆盖掉之前的设置。

Parameters:

groupID - 聊天群组的groupID

exitConversaion

@Deprecated
public static void exitConversaion()

Deprecated. deprecated in 1.2.0. use exitConversation() instead.

推荐在退出聊天会话界面时调用,清除当前正在聊天的对象,用于判断notification是否需要展示

exitConversation

public static void exitConversation()

推荐在退出聊天会话界面时调用,清除当前正在聊天的对象,用于判断notification是否需要展示

createGroup

public static void createGroup(java.lang.String groupName,
                               java.lang.String groupDesc,
                               CreateGroupCallback callback)

创建群组,群组创建成功后,创建者会默认包含在群成员中。该群组默认为私有群组,只能群内成员邀请他人入群,不支持申请入群。
 如果想创建公开群组使用createPublicGroup(String, String, CreateGroupCallback)
 通过GroupBasicInfo.getGroupType()得到群组类型

Parameters:

groupName - 群组名称

groupDesc - 群组描述

callback - 回调接口

createGroup

public static void createGroup(java.lang.String groupName,
                               java.lang.String groupDesc,
                               java.io.File groupAvatarFile,
                               java.lang.String format,
                               CreateGroupCallback callback)

创建群组,群组创建成功后,创建者会默认包含在群成员中。该群组默认为私有群组,只能群内成员邀请他人入群,不支持申请入群。
 如果想创建公开群组使用createPublicGroup(String, String, CreateGroupCallback)
 使用此接口创建群组时可以指定群头像,并且可以指定头像文件在后台存储时的扩展名,如果填空或者不填,则后台存储文件时将没有扩展名。
 通过GroupBasicInfo.getGroupType()得到群组类型

Parameters:

groupName - 群组名称

groupDesc - 群组描述

groupAvatarFile - 群组头像文件

format - 头像文件扩展名,注意名称中不要包括"."

callback - 回调接口

Since:

2.3.0

createPublicGroup

public static void createPublicGroup(java.lang.String groupName,
                                     java.lang.String groupDesc,
                                     CreateGroupCallback callback)

创建公开群组, 群组创建成功后,创建者会默认包含在群成员中。
 公开群组与私有群组的区别是公开群组只有群主和管理员邀请入群才能直接入群,群内其他人员邀请他人入群需要经过群主或管理员审批,
 管理员相关见GroupInfo.addGroupKeeper(List, BasicCallback), 同时公开群组支持申请入群操作applyJoinGroup(long, String, BasicCallback),
 申请入群时也需要群主或管理员审批, 需要审批入群时群主和管理员会收到GroupApprovalEvent事件
 通过GroupBasicInfo.getGroupType()得到群组类型

Parameters:

groupName - 群组名称

groupDesc - 群组描述

callback - 回调接口

Since:

2.4.0

createPublicGroup

public static void createPublicGroup(java.lang.String groupName,
                                     java.lang.String groupDesc,
                                     java.io.File groupAvatarFile,
                                     java.lang.String format,
                                     CreateGroupCallback callback)

创建公开群组,群组创建成功后,创建者会默认包含在群成员中。
 公开群组定义参考createPublicGroup(String, String, CreateGroupCallback)
 使用此接口创建群组时可以指定群头像,并且可以指定头像文件在后台存储时的扩展名,如果填空或者不填,则后台存储文件时将没有扩展名。
 通过GroupBasicInfo.getGroupType()得到群组类型

Parameters:

groupName - 群组名称

groupDesc - 群组描述

groupAvatarFile - 群组头像文件

format - 头像文件扩展名,注意名称中不要包括"."

callback - 回调接口

Since:

2.4.0

applyJoinGroup

public static void applyJoinGroup(long groupID,
                                  java.lang.String reason,
                                  BasicCallback callback)

申请加入群组,只有公开群组(公开群组定义参考createPublicGroup(String, String, CreateGroupCallback)),
 才能发送入群申请

Parameters:

groupID - 申请加入群组的gid

reason - 申请理由,可为null

callback -

Since:

2.4.0

addGroupMembers

public static void addGroupMembers(long groupID,
                                   java.util.List<java.lang.String> userNameList,
                                   BasicCallback callback)

向群组中添加成员。本方法所有传入的username,会默认在本应用下查找,
 若要跨应用添加其他用户请使用addGroupMembers(long, String, List, BasicCallback)

Parameters:

groupID - 群组的groupID

userNameList - 添加进群组的成员username集合

callback - 回调接口

addGroupMembers

public static void addGroupMembers(long groupID,
                                   java.lang.String appKey,
                                   java.util.List<java.lang.String> userNameList,
                                   BasicCallback callback)

向群组中添加成员,通过指定appKey可以实现跨应用添加其他appKey下用户进群组

Parameters:

groupID - 群组的groupID

appKey - 指定的appKey,如果为空则在本应用appKey下查找用户

userNameList - 添加进群组的成员username集合

callback - 回调接口

removeGroupMembers

public static void removeGroupMembers(long groupID,
                                      java.util.List<java.lang.String> userNameList,
                                      BasicCallback callback)

踢出群组成员。本方法所有传入的username,都默认在本应用下查找,
 若要跨应用踢出其他appKey下的群成员请使用removeGroupMembers(long, String, List, BasicCallback)

Parameters:

groupID - 群组的groupID

userNameList - 踢出群组的成员username集合

callback - 回调接口

removeGroupMembers

public static void removeGroupMembers(long groupID,
                                      java.lang.String appKey,
                                      java.util.List<java.lang.String> userNameList,
                                      BasicCallback callback)

踢出群组中成员,通过指定appKey可以实现跨应用踢出群组成员

Parameters:

groupID - 群组的groupID

appKey - 指定的appKey,如果appKey为空则在本应用appKey下查找用户

userNameList - 踢出群组成员的username集合

callback - 回调接口

adminDissolveGroup

public static void adminDissolveGroup(long groupID,
                                      BasicCallback callback)

解散指定的群组,只有群的群主有权限解散。
 群组解散后会以message的形式通知到群内所有成员,类型为EventNotificationContent.EventNotificationType.group_dissolved

Parameters:

groupID - 群组id

callback - 回调

Since:

2.5.0

updateGroupName

public static void updateGroupName(long groupID,
                                   java.lang.String groupName,
                                   BasicCallback callback)

Deprecated. deprecated in 2.3.0,use GroupInfo.updateName(String, BasicCallback) instead

修改群组名称

Parameters:

groupID - 群组的groupID

groupName - 群名称

callback - 回调接口

updateGroupDescription

public static void updateGroupDescription(long groupID,
                                          java.lang.String groupDesc,
                                          BasicCallback callback)

Deprecated. deprecated in 2.3.0,use GroupInfo.updateDescription(String, BasicCallback) instead

修改群组描述

Parameters:

groupID - 群组groupID

groupDesc - 群组描述

callback - 回调接口

exitGroup

public static void exitGroup(long groupId,
                             BasicCallback callback)

退出群组,如果是群主退群,则相关群主权利移交给下一位群成员。如果此时群组中没有其他人,
 则群组将会被解散。

Parameters:

groupId - 群组的groupID

callback - 回调接口

getGroupIDList

public static void getGroupIDList(GetGroupIDListCallback callback)

从服务器获取当前用户所加入的群组的groupID的list,同时会将数据保存到本地数据库

Parameters:

callback - 回调接口

getGroupInfo

public static void getGroupInfo(long groupID,
                                GetGroupInfoCallback callback)

从服务器获取指定群的基本信息。

Parameters:

groupID - 群组的groupID

callback - 回调接口

getGroupMembers

public static void getGroupMembers(long groupID,
                                   GetGroupMembersCallback callback)

从本地获取群成员username list,如果本地不存在则从服务器获取。

Parameters:

groupID - 群组groupID

callback - 回调接口

getPublicGroupListByApp

public static void getPublicGroupListByApp(java.lang.String appKey,
                                           int start,
                                           int count,
                                           RequestCallback<java.util.List<GroupBasicInfo>> callback)

获取指定应用appKey所属下公开群组基本信息, 如果appKey为空则默认使用本应用appKey.
 公开群组定义见createPublicGroup(String, String, CreateGroupCallback),
 群基本信息定义见GroupBasicInfo

Parameters:

appKey - 指定应用的appKey

start - 起始位置

count - 获取个数

callback - 接口回调

Since:

2.4.1

registerEventReceiver

public static void registerEventReceiver(java.lang.Object receiver)

注册事件接收者,具体用法参考官方文档事件处理
 一节

Parameters:

receiver - 消息接收类对象

registerEventReceiver

public static void registerEventReceiver(java.lang.Object receiver,
                                         int priority)

注册事件接收者,具体用法参考官方文档事件处理
 一节

Parameters:

receiver - 事件接收类对象

priority - 事件接收者接收事件的优先级,默认值为0,优先级越高将越先接收到事件。(优先级只对同一个线程模式中的接收者有效)

unRegisterEventReceiver

public static void unRegisterEventReceiver(java.lang.Object receiver)

解绑事件接收者,解绑后接收者将不再接收到事件

Parameters:

receiver - 消息接收类对象

getBlacklist

public static void getBlacklist(GetBlacklistCallback callback)

获取被当前用户加入黑名单的用户列表

Parameters:

callback - 回调接口

addUsersToBlacklist

public static void addUsersToBlacklist(java.util.List<java.lang.String> usernames,
                                       BasicCallback callback)

将用户加入黑名单,对方用户将无法给你发消息。此方法默认在本应用下查找,
 若想要跨应用添加其他appKey下用户请使用addUsersToBlacklist(List, String, BasicCallback)

Parameters:

usernames - 被加入黑名单的用户username列表

callback - 回调接口

addUsersToBlacklist

public static void addUsersToBlacklist(java.util.List<java.lang.String> usernames,
                                       java.lang.String appKey,
                                       BasicCallback callback)

将用户添加进黑名单,对方将无法给你发消息。通过指定appKey可以实现跨应用将用户加入黑名单

Parameters:

usernames - 准备添加进黑名单的username集合

appKey - 指定的appKey,如果appKey为空则在本应用appKey下查找用户

callback - 回调接口

delUsersFromBlacklist

public static void delUsersFromBlacklist(java.util.List<java.lang.String> usernames,
                                         BasicCallback callback)

将用户移出黑名单。本方法所有传入的username,默认移除的是本应用下用户,
 若想要跨应用移除其他appKey下的用户请使用delUsersFromBlacklist(List, String, BasicCallback)

Parameters:

usernames - 被移出黑名单的用户username集合

callback - 回调接口

delUsersFromBlacklist

public static void delUsersFromBlacklist(java.util.List<java.lang.String> usernames,
                                         java.lang.String appKey,
                                         BasicCallback callback)

将用户移出黑名单,通过指定appKey可以实现跨应用将用户移出黑名单

Parameters:

usernames - 准备移出黑名单的username集合

appKey - 指定的appKey,如果appKey为空则在本应用appKey下查找用户

callback - 回调接口

getNoDisturblist

public static void getNoDisturblist(GetNoDisurbListCallback callback)

获取当前用户设置的免打扰名单列表

Parameters:

callback - 回调接口

getBlockedGroupsList

public static void getBlockedGroupsList(GetGroupInfoListCallback callback)

获取当前用户设置的屏蔽群组列表

Parameters:

callback - 回调接口

Since:

2.1.0

setNoDisturbGlobal

public static void setNoDisturbGlobal(int noDisturbGlobal,
                                      BasicCallback callback)

设置全局免打扰标识,设置之后用户在所有设备上收到消息时通知栏都不会弹出消息通知。

Parameters:

noDisturbGlobal - 1表示设置,其他表示取消

callback - 回调接口

getNoDisturbGlobal

public static void getNoDisturbGlobal(IntegerCallback callback)

获取全局免打扰标识,回调结果中,1表示已设置,其他表示未设置。

Parameters:

callback - 回调接口

getAllUnReadMsgCount

public static int getAllUnReadMsgCount()

获取未读消息总数

Returns:

未读消息总数,返回-1代表当前sdk没有初始化,或者处于未登录状态

Since:

2.1.2

getSdkVersionString

public static java.lang.String getSdkVersionString()

获取sdk版本号

Returns:

sdk版本号

Skip navigation links

Overview

Package

Class

Tree

Deprecated

Index

Help

Prev Class

Next Class

Frames

No Frames

All Classes

Summary:

Nested |

Field |

Constr |

Method

Detail:

Field |

Constr |

Method


text:
namespace {

  class ServiceMock : luciconnect::Node {
  public:
    ServiceMock(std::shared_ptr<luciconnect::Connection> connection) : luciconnect::Node(connection) {}

    void Run() {
      this->Connect();

      json register_message = {
        {"serviceName", "addingNumbers"},
        {"description", "Adds two numbers"},
        {"inputs", {
          {"arg1", "number"},
          {"arg2", "number"}
        }},
        {"outputs", {
          {"sum", "number"}
        }},
        {"exampleCall", {
          {"run", "addingNumbers"},
          {"callId", 4386},
          {"arg1", 42},
          {"arg2", 1337}
        }}
      };

      this->SendRun(0, "RemoteRegister", register_message);
    }

  json result;
  bool canceled = false;
  bool error = false;
  int64_t progress = -1;

  protected:
    void HandleRun(int64_t callId, std::string serviceName, json inputs, std::vector<luciconnect::Attachment*> attachments) {
      if (inputs.count("arg1") != 1 || inputs.count("arg2") != 1)
        throw "Erroneous message";

      int64_t arg1 = inputs["arg1"];
      int64_t arg2 = inputs["arg2"];
      json result = {{"sum", arg1 + arg2}};

      this->SendResult(callId, result);
    };

    void HandleCancel(int64_t callId) {
      this->canceled = true;
    };

    void HandleResult(int64_t callId, json result, std::vector<luciconnect::Attachment*> attachments) {
      this->result = result;
      LOG(DEBUG) << "result: " << result.dump();
    };

    void HandleProgress(int64_t callId, int64_t percentage, std::vector<luciconnect::Attachment*> attachments, json intermediateResult) {
      this->progress = percentage;
      LOG(DEBUG) << "progress";
    };

    void HandleError(int64_t callId, std::string error) {
      LOG(DEBUG) << "error: " << error;
      this->error = true;
    };
  };

  // Mock simply sending one message
  class ClientMock : luciconnect::Node {
  public:
    ClientMock(std::shared_ptr<luciconnect::Connection> connection) : luciconnect::Node(connection) {}

    void Run() {
      this->Connect();
      this->SendRun(0, "addingNumbers", {{"arg1", 5}, {"arg2", 7}});
    }

    json result;
    bool canceled = false;
    bool error = false;
    int64_t progress = -1;

  protected:
    void HandleRun(int64_t callId, std::string serviceName, json inputs, std::vector<luciconnect::Attachment*> attachments) {
    };

    void HandleCancel(int64_t callId) {
      this->canceled = true;
    };

    void HandleResult(int64_t callId, json result, std::vector<luciconnect::Attachment*> attachments) {
      this->result = result;
    };

    void HandleProgress(int64_t callId, int64_t percentage, std::vector<luciconnect::Attachment*> attachments, json intermediateResult) {
      this->progress = percentage;
    };

    void HandleError(int64_t callId, std::string error) {
      this->error = true;
    };
  };

  TEST_F(AbstractNodeTest, ClientRunWrongService) {
    std::shared_ptr<luciconnect::Connection> connection = std::make_shared<luciconnect::Connection>("127.0.0.1", 7654);
    ClientMock* node_ = new ClientMock(connection);
    node_->Run();
    std::this_thread::sleep_for(std::chrono::seconds(1));

    ASSERT_EQ(node_->error, true);
  }

  TEST_F(AbstractNodeTest, ServiceRegister) {
    std::shared_ptr<luciconnect::Connection> connection = std::make_shared<luciconnect::Connection>("127.0.0.1", 7654);
    ServiceMock* node_ = new ServiceMock(connection);
    node_->Run();
    std::this_thread::sleep_for(std::chrono::seconds(1));

    ASSERT_EQ(node_->result.count("registeredName"), 1);
    ASSERT_EQ(node_->result["registeredName"], "addingNumbers");
  }

  TEST_F(AbstractNodeTest, ClientServiceRun) {
    std::shared_ptr<luciconnect::Connection> connection1 = std::make_shared<luciconnect::Connection>("127.0.0.1", 7654);
    ServiceMock* service = new ServiceMock(connection1);
    service->Run();
    std::this_thread::sleep_for(std::chrono::seconds(1));

    std::shared_ptr<luciconnect::Connection> connection2 = std::make_shared<luciconnect::Connection>("127.0.0.1", 7654);
    ClientMock* client = new ClientMock(connection2);
    client->Run();
    std::this_thread::sleep_for(std::chrono::seconds(1));

    json expected_result = {{"sum", 12}};
    ASSERT_EQ(client->result, expected_result);
  }
}


text:
/**
 * @file
 *
 * @date Sep 25, 2013
 * @author Anton Bondarev
 * @author Alexander Kalmuk
 */
#include <errno.h>
#include <time.h>
#include <stdlib.h>
#include <util/math.h>

#include <kernel/time/ktime.h>
#include <kernel/time/clock_source.h>
#include <embox/unit.h>
#include <util/log.h>

EMBOX_UNIT_INIT(nanosleep_init);

struct hw_time {
	uint32_t jiffies;
	uint32_t cycles;
};

static struct timespec nanosleep_waste_time;
static struct clock_source *nanosleep_cs;
static unsigned int nanosleep_cs_load;

static inline struct timespec get_timetosleep(const struct timespec *rqtp);
static void cs_nanospin(struct clock_source *cs, struct hw_time *hw);

/* XXX: nanosleep is irq sensetive, i.e. rqtp can be exceeded when irq occur
 * after ksleep and before returning from nanosleep. */
int nanosleep(const struct timespec *rqtp, struct timespec *rmtp) {
	time64_t start, hw_cycles;
	uint32_t ms_tosleep;
	int remaining_cycles;
	struct timespec timetosleep;
	struct hw_time hw;

	if (rqtp->tv_sec == 0 && rqtp->tv_nsec == 0) {
		return ksleep(0);
	}

	timetosleep = get_timetosleep(rqtp);
	hw_cycles = timespec_to_hw(&timetosleep, nanosleep_cs->counter_device->cycle_hz);

	start = clock_source_get_hwcycles(nanosleep_cs);
	ms_tosleep = timetosleep.tv_sec * 1000 + timetosleep.tv_nsec / 1000000;
	if (ms_tosleep > 0) {
		ksleep(ms_tosleep - 1);
	}

	remaining_cycles = (int64_t)hw_cycles - (clock_source_get_hwcycles(nanosleep_cs) - start);

	if (remaining_cycles > 0) {
		int tmp;

		tmp = remaining_cycles + nanosleep_cs->counter_device->read(nanosleep_cs);
		hw.jiffies = nanosleep_cs->event_device->jiffies + tmp / nanosleep_cs_load;
		hw.cycles = tmp % nanosleep_cs_load;

		cs_nanospin(nanosleep_cs, &hw);
	}

	return ENOERR;
}

static void cs_nanospin(struct clock_source *cs, struct hw_time *hw) {
	while(cs->event_device->jiffies < hw->jiffies);
	while((cs->event_device->jiffies == hw->jiffies) && (cs->counter_device->read(cs) <= hw->cycles));
}

static inline struct timespec get_timetosleep(const struct timespec *rqtp) {
	struct timespec ret;

	if (rqtp->tv_sec > 0 || rqtp->tv_nsec > nanosleep_waste_time.tv_nsec) {
		ret = timespec_sub(*rqtp, nanosleep_waste_time);
	} else {
		ret = *rqtp;
	}

	return ret;
}

/**
 * Main logic:
 * 1. Calculate execution time of ktime_get_timespec (i.e. overhead)
 * 2. Then we can calculate execution of nanosleep without going in ksleep and without spin (i.e. overhead).
 * 3. When we get T ns as argument in nanosleep, we substitute overhead calculated in step 2 from T.
 */
static int nanosleep_calibrate(void) {
	struct timespec t, m, ts;
	struct timespec rqtp = { .tv_sec = 0, .tv_nsec = 1 };

	ktime_get_timespec(&t);
	ktime_get_timespec(&m);
	timespecsub(&m, &t, &ts);

	log_info("ktime_get_timespec() execution ns: %d", (int) ts.tv_nsec);

	ktime_get_timespec(&t);
	nanosleep(&rqtp, NULL);
	ktime_get_timespec(&m);
	timespecsub(&m, &t, &rqtp);

	timespecsub(&rqtp, &ts, &nanosleep_waste_time);

	log_info("nanosleep execution ns: %d", (int) nanosleep_waste_time.tv_nsec);

	return 0;
}

static int nanosleep_init(void) {
	nanosleep_cs = clock_source_get_best(CS_WITH_IRQ);
	if (NULL == nanosleep_cs) {
		return -1;
	}
	nanosleep_cs_load =
			nanosleep_cs->counter_device->cycle_hz / nanosleep_cs->event_device->event_hz;

	nanosleep_calibrate();

	return 0;
}