Пример #1
0
        /// <summary>
        ///     Adds the specified <paramref name="slimResponse"/> to the list of responses after applying the filters specified in
        ///     the search options.
        /// </summary>
        /// <param name="slimResponse">The response to add.</param>
        internal void AddResponse(SearchResponseSlim slimResponse)
        {
            // ensure the search is still active, the token matches and that the response meets basic filtering criteria we check
            // the slim response for fitness prior to extracting the file list from it for performance reasons.
            if (State.HasFlag(SearchStates.InProgress) && slimResponse.Token == Token && SlimResponseMeetsOptionCriteria(slimResponse))
            {
                // extract the file list from the response and filter it
                var fullResponse  = new SearchResponse(slimResponse);
                var filteredFiles = fullResponse.Files.Where(f => Options.FileFilter?.Invoke(f) ?? true);

                fullResponse = new SearchResponse(fullResponse, filteredFiles);

                // ensure the filtered file count still meets the response criteria
                if ((Options.FilterResponses && fullResponse.FileCount < Options.MinimumResponseFileCount) || !(Options.ResponseFilter?.Invoke(fullResponse) ?? true))
                {
                    return;
                }

                Interlocked.Increment(ref resultCount);
                Interlocked.Add(ref resultFileCount, fullResponse.Files.Count);

                ResponseBag.Add(fullResponse);

                ResponseReceived?.Invoke(fullResponse);
                SearchTimeoutTimer.Reset();

                if (resultCount >= Options.ResponseLimit)
                {
                    Complete(SearchStates.ResponseLimitReached);
                }
                else if (resultFileCount >= Options.FileLimit)
                {
                    Complete(SearchStates.FileLimitReached);
                }
            }
        }
Пример #2
0
 /// <summary>
 ///     Initializes a new instance of the <see cref="SearchResponse"/> class.
 /// </summary>
 /// <param name="searchResponse">An existing instance from which to copy properties.</param>
 /// <param name="fileList">The file list with which to replace the existing file list.</param>
 /// <param name="lockedFileList">The optional locked file list with which to replace the existing locked file list.</param>
 internal SearchResponse(SearchResponse searchResponse, IEnumerable <File> fileList, IEnumerable <File> lockedFileList = null)
     : this(searchResponse.Username, searchResponse.Token, searchResponse.FreeUploadSlots, searchResponse.UploadSpeed, searchResponse.QueueLength, fileList, lockedFileList)
 {
 }
Пример #3
0
 /// <summary>
 ///     Initializes a new instance of the <see cref="SearchResponseReceivedEventArgs"/> class.
 /// </summary>
 /// <param name="response">The search response which raised the event.</param>
 /// <param name="search">The search instance with which to initialize data.</param>
 internal SearchResponseReceivedEventArgs(SearchResponse response, Search search)
     : base(search)
 {
     Response = response;
 }
Пример #4
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);
        }