Пример #1
0
        /// <summary>
        ///     Sends the pending response matching the specified <paramref name="responseToken"/>, if one exists.
        /// </summary>
        /// <remarks>
        ///     This overload is called by the listener when an incoming connection is established with a pierce firewall token,
        ///     and if that token doesn't match a pending solicitation, and if the token matches a cached search response.  In this case,
        ///     the connection is retrieved from the cache and used to send the response.
        /// </remarks>
        /// <param name="responseToken">The token matching the pending response to send.</param>
        /// <returns>The operation context, including a value indicating whether a response was successfully sent.</returns>
        public async Task <bool> TryRespondAsync(int responseToken)
        {
            if (SoulseekClient.Options.SearchResponseCache != default)
            {
                bool cached;
                (string Username, int Token, string Query, SearchResponse SearchResponse)record;

                try
                {
                    cached = SoulseekClient.Options.SearchResponseCache.TryRemove(responseToken, out record);
                }
                catch (Exception ex)
                {
                    Diagnostic.Warning($"Error retrieving cached search response {responseToken}: {ex.Message}", ex);
                    return(false);
                }

                if (cached)
                {
                    var(username, token, query, searchResponse) = record;

                    try
                    {
                        var peerConnection = await SoulseekClient.PeerConnectionManager.GetCachedMessageConnectionAsync(username).ConfigureAwait(false);

                        await peerConnection.WriteAsync(searchResponse.ToByteArray()).ConfigureAwait(false);

                        Diagnostic.Debug($"Sent cached response {responseToken} containing {searchResponse.FileCount + searchResponse.LockedFileCount} files to {username} for query '{query}' with token {token}");
                        ResponseDelivered?.Invoke(this, new SearchRequestResponseEventArgs(username, token, query, searchResponse));
                        return(true);
                    }
                    catch (Exception ex)
                    {
                        Diagnostic.Debug($"Failed to send cached search response {responseToken} to {username} for query '{query}' with token {token}: {ex.Message}", ex);
                        ResponseDeliveryFailed?.Invoke(this, new SearchRequestResponseEventArgs(username, token, query, searchResponse));
                    }
                }
            }

            return(false);
        }
Пример #2
0
        /// <summary>
        ///     Responds to the given search request, if a response could be resolved and matche(s) were found.
        /// </summary>
        /// <param name="username">The username of the requesting user.</param>
        /// <param name="token">The token for the search request.</param>
        /// <param name="query">The search query.</param>
        /// <returns>The operation context, including a value indicating whether a response was successfully sent.</returns>
        public async Task <bool> TryRespondAsync(string username, int token, string query)
        {
            RequestReceived?.Invoke(this, new SearchRequestEventArgs(username, token, query));

            if (SoulseekClient.Options.SearchResponseResolver == default)
            {
                return(false);
            }

            SearchResponse searchResponse = null;

            try
            {
                searchResponse = await SoulseekClient.Options.SearchResponseResolver(username, token, SearchQuery.FromText(query)).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Diagnostic.Warning($"Error resolving search response for query '{query}' requested by {username} with token {token}: {ex.Message}", ex);
                return(false);
            }

            if (searchResponse == null || searchResponse.FileCount + searchResponse.LockedFileCount <= 0)
            {
                return(false);
            }

            try
            {
                Diagnostic.Debug($"Resolved {searchResponse.FileCount} files for query '{query}' with token {token} from {username}");

                var endpoint = await SoulseekClient.GetUserEndPointAsync(username).ConfigureAwait(false);

                var responseToken = SoulseekClient.GetNextToken();

                IMessageConnection peerConnection = default;

                try
                {
                    // attempt to connect and send the results immediately. either a direct connection succeeds, or a user
                    // responds to a solicited connection request prior to the configured connection timeout.
                    peerConnection = await SoulseekClient.PeerConnectionManager.GetOrAddMessageConnectionAsync(username, endpoint, solicitationToken : responseToken, CancellationToken.None).ConfigureAwait(false);
                }
                catch
                {
                    // direct connection failed, and user did not respond to the solicited connection request before the timeout,
                    // but may respond later. cache the result along with the solicitation token that was sent so we can attempt a
                    // "second chance" delivery of results
                    if (SoulseekClient.Options.SearchResponseCache != default)
                    {
                        try
                        {
                            SoulseekClient.Options.SearchResponseCache.AddOrUpdate(responseToken, (username, token, query, searchResponse));
                            Diagnostic.Debug($"Failed to connect to {username} with solicitation token {responseToken} to deliver search results for query '{query}' with token {token}.  Cached response for potential delayed delivery.");
                        }
                        catch (Exception ex)
                        {
                            Diagnostic.Warning($"Error caching undelivered search response {responseToken} for query '{query}' requested by {username} with token {token}: {ex.Message}", ex);
                        }
                    }

                    throw;
                }

                await peerConnection.WriteAsync(searchResponse.ToByteArray()).ConfigureAwait(false);

                Diagnostic.Debug($"Sent response containing {searchResponse.FileCount + searchResponse.LockedFileCount} files to {username} for query '{query}' with token {token}");
                ResponseDelivered?.Invoke(this, new SearchRequestResponseEventArgs(username, token, query, searchResponse));

                return(true);
            }
            catch (Exception ex)
            {
                Diagnostic.Debug($"Failed to send search response to {username} for query '{query}' with token {token}: {ex.Message}", ex);
            }

            return(false);
        }