コード例 #1
0
 protected override MalPositiveFeedbackInput GetRecSourceInputFromRequest(MalUserListEntries animeList, GetMalRecsRequest recRequest)
 {
     if (recRequest.TargetScore != null)
     {
         return(new MalPositiveFeedbackInput(animeList, recRequest.TargetScore.Value));
     }
     else
     {
         return(new MalPositiveFeedbackInput(animeList, recRequest.TargetFraction.Value));
     }
 }
コード例 #2
0
 protected override MalAnimeRecsInput GetRecSourceInputFromRequest(MalUserListEntries animeList, GetMalRecsRequest recRequest)
 {
     if (recRequest.TargetScore != null)
     {
         return(new MalAnimeRecsInput(animeList, targetScore: recRequest.TargetScore.Value));
     }
     else
     {
         return(new MalAnimeRecsInput(animeList, targetFraction: (double)recRequest.TargetFraction.Value));
     }
 }
コード例 #3
0
        public DTO.GetMalRecsResponse GetRecommendations(MalUserListEntries animeList, GetMalRecsRequest recRequest, CancellationToken cancellationToken)
        {
            // Ignore the cancellation token - we're only cancelling on service shut down, and getting recommendations should be quick.
            // If getting recommendations takes longer than the final connection drain time limit, something is wrong.
            TInput recSourceInput             = GetRecSourceInputFromRequest(animeList, recRequest);
            TRecommendationResults recResults = UnderlyingRecSource.GetRecommendations(recSourceInput, recRequest.NumRecsDesired);

            List <TDtoRec> dtoRecs = new List <TDtoRec>();
            Dictionary <int, DTO.MalAnime> animes = new Dictionary <int, DTO.MalAnime>();

            foreach (TRecommendation rec in recResults)
            {
                TDtoRec dtoRec = new TDtoRec()
                {
                    MalAnimeId = rec.ItemId,
                };
                animes[rec.ItemId] = new DTO.MalAnime(rec.ItemId, Animes[rec.ItemId].Title, Animes[rec.ItemId].Type);

                SetSpecializedRecommendationProperties(dtoRec, rec);

                dtoRecs.Add(dtoRec);
            }

            TResponse response = new TResponse();

            response.RecommendationType = RecommendationType;
            response.Recommendations    = dtoRecs;
            SetSpecializedExtraResponseProperties(response, recResults);

            HashSet <int> extraAnimesToReturn = GetExtraAnimesToReturn(recResults);

            foreach (int extraAnimeId in extraAnimesToReturn)
            {
                if (!animes.ContainsKey(extraAnimeId))
                {
                    animes[extraAnimeId] = new DTO.MalAnime(extraAnimeId, Animes[extraAnimeId].Title, Animes[extraAnimeId].Type);
                }
            }

            response.Animes = animes.Values.ToList();

            return(response);
        }
コード例 #4
0
 protected override MalUserListEntries GetRecSourceInputFromRequest(MalUserListEntries animeList, GetMalRecsRequest recRequest)
 {
     return(animeList);
 }
コード例 #5
0
 /// <summary>
 /// Converts the user's anime list into the input used by the rec source.
 /// </summary>
 /// <param name="animeList"></param>
 /// <param name="recRequest"></param>
 /// <param name="caster"></param>
 /// <returns></returns>
 protected abstract TInput GetRecSourceInputFromRequest(MalUserListEntries animeList, GetMalRecsRequest recRequest);
コード例 #6
0
        public async Task <MalTrainingData> LoadMalTrainingDataAsync(CancellationToken cancellationToken)
        {
            // Load all anime, users, and entries in parallel, then combine them into training data.
            try
            {
                Dictionary <int, MalAnime>  animes;
                Dictionary <int, mal_user>  dbUsers;
                IList <mal_list_entry_slim> dbEntrySlurp;

                using (CancellationTokenSource faultCanceler = new CancellationTokenSource())
                    using (CancellationTokenSource faultOrUserCancel = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, faultCanceler.Token))
                    {
                        Task <Dictionary <int, MalAnime> > animeTask = AsyncUtils.EnsureExceptionsWrapped(
                            () => SlurpAnimeAsync(faultOrUserCancel.Token));
                        CancellableTask cancellableAnimeTask = new CancellableTask(animeTask, faultCanceler);

                        Task <Dictionary <int, mal_user> > userTask = AsyncUtils.EnsureExceptionsWrapped(
                            () => SlurpUsersAsync(faultOrUserCancel.Token));
                        CancellableTask cancellableUserTask = new CancellableTask(userTask, faultCanceler);

                        Task <IList <mal_list_entry_slim> > entryTask = AsyncUtils.EnsureExceptionsWrapped(
                            () => SlurpEntriesAsync(faultOrUserCancel.Token));
                        CancellableTask cancellableEntryTask = new CancellableTask(entryTask, faultCanceler);

                        await AsyncUtils.WhenAllCancelOnFirstExceptionDontWaitForCancellations(cancellableEntryTask, cancellableAnimeTask, cancellableUserTask).ConfigureAwait(false);

                        animes       = animeTask.Result;
                        dbUsers      = userTask.Result;
                        dbEntrySlurp = entryTask.Result;
                    }

                Logging.Log.Debug("Processing list entries from the database.");
                long entryCount = 0;

                Dictionary <int, List <ReadOnlyMalListEntryDictionary.ListEntryAndAnimeId> > entriesByUser =
                    new Dictionary <int, List <ReadOnlyMalListEntryDictionary.ListEntryAndAnimeId> >();

                foreach (mal_list_entry_slim dbEntry in dbEntrySlurp)
                {
                    entryCount++;
                    mal_user dbUser;
                    if (!dbUsers.TryGetValue(dbEntry.mal_user_id, out dbUser) || !animes.ContainsKey(dbEntry.mal_anime_id))
                    {
                        // Entry for an anime or user that wasn't in the database...there must have been an update going on between the time we got users, anime, and list entries
                        continue;
                    }
                    List <ReadOnlyMalListEntryDictionary.ListEntryAndAnimeId> animeList;
                    if (!entriesByUser.TryGetValue(dbEntry.mal_user_id, out animeList))
                    {
                        animeList = new List <ReadOnlyMalListEntryDictionary.ListEntryAndAnimeId>();
                        entriesByUser[dbEntry.mal_user_id] = animeList;
                    }

                    animeList.Add(new ReadOnlyMalListEntryDictionary.ListEntryAndAnimeId(
                                      animeId: dbEntry.mal_anime_id,
                                      entry: new MalListEntry(
                                          rating: (byte?)dbEntry.rating,
                                          status: (CompletionStatus)dbEntry.mal_list_entry_status_id,
                                          numEpisodesWatched: dbEntry.num_episodes_watched
                                          )
                                      ));
                }

                Dictionary <int, MalUserListEntries> users = new Dictionary <int, MalUserListEntries>(dbUsers.Count);
                foreach (int userId in entriesByUser.Keys)
                {
                    List <ReadOnlyMalListEntryDictionary.ListEntryAndAnimeId> animeList = entriesByUser[userId];
                    animeList.Capacity = animeList.Count;
                    ReadOnlyMalListEntryDictionary listEntries = new ReadOnlyMalListEntryDictionary(animeList);
                    users[userId] = new MalUserListEntries(listEntries, animes, dbUsers[userId].mal_name, okToRecommendPredicate: null);
                }

                Logging.Log.DebugFormat("Done processing {0} list entries.", entryCount);

                return(new MalTrainingData(users, animes));
            }
            catch (Exception ex) when(!(ex is OperationCanceledException))
            {
                throw new Exception(string.Format("Error loading MAL training data: {0}", ex.Message), ex);
            }
        }
コード例 #7
0
        public async Task <GetMalRecsResponse> GetMalRecsAsync(GetMalRecsRequest request, CancellationToken cancellationToken)
        {
            request.AssertArgumentNotNull("request");
            request.RecSourceName.AssertArgumentNotNull("request.RecSourceName");
            request.AnimeList.AssertArgumentNotNull("request.AnimeList");
            request.AnimeList.Entries.AssertArgumentNotNull("request.AnimeList.Entries");

            if (request.TargetScore == null && request.TargetFraction == null)
            {
                Error error = new Error(ErrorCodes.InvalidArgument, "Payload.TargetScore or Payload.TargetFraction must be set.");
                throw new RecServiceErrorException(error);
            }

            string targetScoreString;

            if (request.TargetFraction != null)
            {
                targetScoreString = request.TargetFraction.Value.ToString("P2");
            }
            else
            {
                targetScoreString = request.TargetScore.Value.ToString();
            }

            Logging.Log.InfoFormat("Request for {0} MAL recs using rec source {1}. User has {2} anime list entries. Target score is {3}.",
                                   request.NumRecsDesired, request.RecSourceName, request.AnimeList.Entries.Count, targetScoreString);

            // Acquire read lock on rec sources
            const int lockTimeoutInSeconds = 3;

            using (CancellationTokenSource lockTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(lockTimeoutInSeconds)))
                using (CancellationTokenSource lockCancel = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, lockTimeout.Token))
                {
                    bool gotLock = false;
                    try
                    {
                        using (var recSourcesLock = await m_recSourcesLockAsync.EnterReadLockAsync(lockCancel.Token).ConfigureAwait(false))
                        {
                            gotLock = true;
                            // Get rec source by name
                            if (!m_recSources.ContainsKey(request.RecSourceName))
                            {
                                Error error = new Error(errorCode: ErrorCodes.NoSuchRecSource,
                                                        message: string.Format("No rec source called \"{0}\" is loaded.", request.RecSourceName));
                                throw new RecServiceErrorException(error);
                            }
                            ITrainableJsonRecSource recSource = m_recSources[request.RecSourceName];

                            // Convert DTO anime list to RecEngine anime list

                            Dictionary <int, AnimeRecs.RecEngine.MAL.MalListEntry> entries = new Dictionary <int, RecEngine.MAL.MalListEntry>();
                            foreach (AnimeRecs.RecService.DTO.MalListEntry dtoEntry in request.AnimeList.Entries)
                            {
                                AnimeRecs.RecEngine.MAL.MalListEntry recEngineEntry = new RecEngine.MAL.MalListEntry(dtoEntry.Rating, dtoEntry.Status, dtoEntry.NumEpisodesWatched);
                                entries[dtoEntry.MalAnimeId] = recEngineEntry;
                            }
                            MalUserListEntries animeList = new MalUserListEntries(ratings: entries, animes: m_animes,
                                                                                  malUsername: null, prerequisites: m_prereqs);

                            Stopwatch          timer    = Stopwatch.StartNew();
                            GetMalRecsResponse response = recSource.GetRecommendations(animeList, request, cancellationToken);
                            timer.Stop();

                            Logging.Log.InfoFormat("Got recommendations from rec source {0}. Took {1}.", request.RecSourceName, timer.Elapsed);
                            return(response);
                        }
                    }
                    catch (OperationCanceledException)
                    {
                        // If we couldn't get a read lock within 3 seconds, a reload/retrain is probably going on
                        if (!gotLock)
                        {
                            Error error = new Error(errorCode: ErrorCodes.Maintenance,
                                                    message: "The rec service is currently undergoing maintenance and cannot respond to rec requests.");
                            throw new RecServiceErrorException(error);
                        }
                        else
                        {
                            throw;
                        }
                    }
                }
        }