| | 1 | | using Cysharp.Threading.Tasks; |
| | 2 | | using DCL; |
| | 3 | | using DCL.Tasks; |
| | 4 | | using DCLServices.Lambdas; |
| | 5 | | using MainScripts.DCL.Controllers.HotScenes; |
| | 6 | | using System; |
| | 7 | | using System.Collections.Generic; |
| | 8 | | using System.Threading; |
| | 9 | | using UnityEngine; |
| | 10 | |
|
| | 11 | | namespace DCLServices.PlacesAPIService |
| | 12 | | { |
| | 13 | | public interface IPlacesAPIService: IService |
| | 14 | | { |
| | 15 | | UniTask<(IReadOnlyList<IHotScenesController.PlaceInfo> places, int total)> SearchPlaces(string searchText, int p |
| | 16 | |
|
| | 17 | | UniTask<(IReadOnlyList<IHotScenesController.PlaceInfo> places, int total)> GetMostActivePlaces(int pageNumber, i |
| | 18 | |
|
| | 19 | | UniTask<IHotScenesController.PlaceInfo> GetPlace(Vector2Int coords, CancellationToken ct, bool renewCache = fals |
| | 20 | |
|
| | 21 | | UniTask<IHotScenesController.PlaceInfo> GetPlace(string placeUUID, CancellationToken ct, bool renewCache = false |
| | 22 | |
|
| | 23 | | UniTask<IReadOnlyList<IHotScenesController.PlaceInfo>> GetFavorites(int pageNumber, int pageSize, CancellationTo |
| | 24 | |
|
| | 25 | | UniTask<List<IHotScenesController.PlaceInfo>> GetPlacesByCoordsList(IEnumerable<Vector2Int> coordsList, Cancella |
| | 26 | |
|
| | 27 | | UniTask SetPlaceFavorite(string placeUUID, bool isFavorite, CancellationToken ct); |
| | 28 | | UniTask SetPlaceVote(bool? isUpvote, string placeUUID, CancellationToken ct); |
| | 29 | | UniTask SetPlaceFavorite(Vector2Int coords, bool isFavorite, CancellationToken ct); |
| | 30 | |
|
| | 31 | | UniTask<bool> IsFavoritePlace(IHotScenesController.PlaceInfo placeInfo, CancellationToken ct, bool renewCache = |
| | 32 | | UniTask<bool> IsFavoritePlace(Vector2Int coords, CancellationToken ct, bool renewCache = false); |
| | 33 | | UniTask<bool> IsFavoritePlace(string placeUUID, CancellationToken ct, bool renewCache = false); |
| | 34 | |
|
| | 35 | | UniTask<IReadOnlyList<string>> GetPointsOfInterestCoords(CancellationToken ct, bool renewCache = false); |
| | 36 | |
|
| | 37 | | UniTask ReportPlace(PlaceContentReportPayload placeContentReportPayload, CancellationToken ct); |
| | 38 | | UniTask<List<PlaceCategoryInfo>> GetPlaceCategories(CancellationToken ct, bool renewCache = false); |
| | 39 | | } |
| | 40 | |
|
| | 41 | | public class PlacesAPIService : IPlacesAPIService, ILambdaServiceConsumer<IHotScenesController.PlacesAPIResponse> |
| | 42 | | { |
| | 43 | | private readonly IPlacesAPIClient client; |
| | 44 | |
|
| 425 | 45 | | internal readonly Dictionary<string, LambdaResponsePagePointer<IHotScenesController.PlacesAPIResponse>> activePl |
| 425 | 46 | | internal readonly Dictionary<string, IHotScenesController.PlaceInfo> placesById = new (); |
| 425 | 47 | | internal readonly Dictionary<Vector2Int, IHotScenesController.PlaceInfo> placesByCoords = new (); |
| | 48 | | private List<string> pointsOfInterestCoords; |
| | 49 | | private List<PlaceCategoryInfo> placeCategories; |
| | 50 | |
|
| | 51 | | //Favorites |
| 425 | 52 | | internal bool composedFavoritesDirty = true; |
| 425 | 53 | | internal readonly List<IHotScenesController.PlaceInfo> composedFavorites = new (); |
| | 54 | | internal UniTaskCompletionSource<List<IHotScenesController.PlaceInfo>> serverFavoritesCompletionSource = null; |
| 425 | 55 | | private DateTime serverFavoritesLastRetrieval = DateTime.MinValue; |
| 425 | 56 | | internal readonly Dictionary<string, bool> localFavorites = new (); |
| | 57 | |
|
| 425 | 58 | | private readonly CancellationTokenSource disposeCts = new (); |
| | 59 | |
|
| 425 | 60 | | public PlacesAPIService(IPlacesAPIClient client) |
| | 61 | | { |
| 425 | 62 | | this.client = client; |
| 425 | 63 | | } |
| 425 | 64 | | public void Initialize() { } |
| | 65 | |
|
| | 66 | | public async UniTask<(IReadOnlyList<IHotScenesController.PlaceInfo> places, int total)> SearchPlaces(string sear |
| | 67 | | { |
| 0 | 68 | | IHotScenesController.PlacesAPIResponse placesAPIResponse = await client.SearchPlaces(searchText, pageNumber, |
| 0 | 69 | | return (placesAPIResponse.data, placesAPIResponse.total); |
| 0 | 70 | | } |
| | 71 | |
|
| | 72 | | public async UniTask<(IReadOnlyList<IHotScenesController.PlaceInfo> places, int total)> GetMostActivePlaces(int |
| | 73 | | { |
| 0 | 74 | | var createNewPointer = false; |
| | 75 | |
|
| 0 | 76 | | if (!activePlacesPagePointers.TryGetValue($"{pageSize}_{filter}_{sort}", out var pagePointer)) { createNewPo |
| 0 | 77 | | else if (renewCache) |
| | 78 | | { |
| 0 | 79 | | pagePointer.Dispose(); |
| 0 | 80 | | activePlacesPagePointers.Remove($"{pageSize}_{filter}_{sort}"); |
| 0 | 81 | | createNewPointer = true; |
| | 82 | | } |
| | 83 | |
|
| 0 | 84 | | if (createNewPointer) |
| | 85 | | { |
| 0 | 86 | | activePlacesPagePointers[$"{pageSize}_{filter}_{sort}"] = pagePointer = new LambdaResponsePagePointer<IH |
| | 87 | | $"", // not needed, the consumer will compose the URL |
| | 88 | | pageSize, disposeCts.Token, this, TimeSpan.FromSeconds(30)); |
| | 89 | | } |
| | 90 | |
|
| 0 | 91 | | (IHotScenesController.PlacesAPIResponse response, bool _) = await pagePointer.GetPageAsync(pageNumber, ct, n |
| | 92 | |
|
| 0 | 93 | | foreach (IHotScenesController.PlaceInfo place in response.data) |
| | 94 | | { |
| 0 | 95 | | CachePlace(place); |
| | 96 | | } |
| | 97 | |
|
| 0 | 98 | | return (response.data, response.total); |
| 0 | 99 | | } |
| | 100 | |
|
| | 101 | | public async UniTask<IHotScenesController.PlaceInfo> GetPlace(Vector2Int coords, CancellationToken ct, bool rene |
| | 102 | | { |
| 0 | 103 | | if (renewCache) |
| 0 | 104 | | placesByCoords.Remove(coords); |
| 0 | 105 | | else if (placesByCoords.TryGetValue(coords, out var placeInfo)) |
| 0 | 106 | | return placeInfo; |
| | 107 | |
|
| 0 | 108 | | var place = await client.GetPlace(coords, ct); |
| 0 | 109 | | CachePlace(place); |
| 0 | 110 | | return place; |
| 0 | 111 | | } |
| | 112 | |
|
| | 113 | | public async UniTask<IHotScenesController.PlaceInfo> GetPlace(string placeUUID, CancellationToken ct, bool renew |
| | 114 | | { |
| 0 | 115 | | if (renewCache) |
| 0 | 116 | | placesById.Remove(placeUUID); |
| 0 | 117 | | else if (placesById.TryGetValue(placeUUID, out var placeInfo)) |
| 0 | 118 | | return placeInfo; |
| | 119 | |
|
| 0 | 120 | | var place = await client.GetPlace(placeUUID, ct); |
| 0 | 121 | | CachePlace(place); |
| 0 | 122 | | return place; |
| 0 | 123 | | } |
| | 124 | |
|
| | 125 | | public async UniTask<List<IHotScenesController.PlaceInfo>> GetPlacesByCoordsList(IEnumerable<Vector2Int> coordsL |
| | 126 | | { |
| 0 | 127 | | List<IHotScenesController.PlaceInfo> alreadyCachedPlaces = new (); |
| 0 | 128 | | List<Vector2Int> coordsToRequest = new (); |
| | 129 | |
|
| 0 | 130 | | foreach (Vector2Int coords in coordsList) |
| | 131 | | { |
| 0 | 132 | | if (renewCache) |
| | 133 | | { |
| 0 | 134 | | placesByCoords.Remove(coords); |
| 0 | 135 | | coordsToRequest.Add(coords); |
| | 136 | | } |
| | 137 | | else |
| | 138 | | { |
| 0 | 139 | | if (placesByCoords.TryGetValue(coords, out var placeInfo)) |
| 0 | 140 | | alreadyCachedPlaces.Add(placeInfo); |
| | 141 | | else |
| 0 | 142 | | coordsToRequest.Add(coords); |
| | 143 | | } |
| | 144 | | } |
| | 145 | |
|
| 0 | 146 | | var places = new List<IHotScenesController.PlaceInfo>(); |
| 0 | 147 | | if (coordsToRequest.Count > 0) |
| | 148 | | { |
| 0 | 149 | | places = await client.GetPlacesByCoordsList(coordsToRequest, ct); |
| 0 | 150 | | foreach (var place in places) |
| 0 | 151 | | CachePlace(place); |
| | 152 | | } |
| | 153 | |
|
| 0 | 154 | | places.AddRange(alreadyCachedPlaces); |
| | 155 | |
|
| 0 | 156 | | return places; |
| 0 | 157 | | } |
| | 158 | |
|
| | 159 | | public async UniTask<IReadOnlyList<IHotScenesController.PlaceInfo>> GetFavorites(int pageNumber, int pageSize, C |
| | 160 | | { |
| | 161 | | const int CACHE_EXPIRATION = 30; // Seconds |
| | 162 | |
|
| | 163 | | // We need to pass the source to avoid conflicts with parallel calls forcing renewCache |
| | 164 | | async UniTask RetrieveFavorites(UniTaskCompletionSource<List<IHotScenesController.PlaceInfo>> source) |
| | 165 | | { |
| | 166 | | List<IHotScenesController.PlaceInfo> favorites; |
| | 167 | | // We dont use the ct param, otherwise the whole flow would be cancel if the first call is cancelled |
| 0 | 168 | | if (pageNumber == -1 && pageSize == -1) |
| | 169 | | { |
| 0 | 170 | | favorites = await client.GetAllFavorites(ct); |
| | 171 | | } |
| | 172 | | else |
| | 173 | | { |
| 0 | 174 | | favorites = await client.GetFavorites(pageNumber, pageSize, disposeCts.Token); |
| | 175 | | } |
| 0 | 176 | | foreach (IHotScenesController.PlaceInfo place in favorites) |
| | 177 | | { |
| 0 | 178 | | CachePlace(place); |
| | 179 | | } |
| 0 | 180 | | composedFavoritesDirty = true; |
| 0 | 181 | | source.TrySetResult(favorites); |
| 0 | 182 | | } |
| | 183 | |
|
| 0 | 184 | | if (serverFavoritesCompletionSource == null || renewCache || (DateTime.Now - serverFavoritesLastRetrieval) > |
| | 185 | | { |
| 0 | 186 | | localFavorites.Clear(); |
| 0 | 187 | | serverFavoritesLastRetrieval = DateTime.Now; |
| 0 | 188 | | serverFavoritesCompletionSource = new UniTaskCompletionSource<List<IHotScenesController.PlaceInfo>>(); |
| 0 | 189 | | RetrieveFavorites(serverFavoritesCompletionSource).Forget(); |
| | 190 | | } |
| | 191 | |
|
| 0 | 192 | | List<IHotScenesController.PlaceInfo> serverFavorites = await serverFavoritesCompletionSource.Task.AttachExte |
| | 193 | |
|
| 0 | 194 | | if (!composedFavoritesDirty) |
| 0 | 195 | | return composedFavorites; |
| | 196 | |
|
| 0 | 197 | | composedFavorites.Clear(); |
| 0 | 198 | | foreach (IHotScenesController.PlaceInfo serverFavorite in serverFavorites) |
| | 199 | | { |
| | 200 | | //skip if it's already in the local favorites cache, it will be added (or not) later |
| 0 | 201 | | if(localFavorites.ContainsKey(serverFavorite.id)) |
| | 202 | | continue; |
| 0 | 203 | | composedFavorites.Add(serverFavorite); |
| | 204 | | } |
| | 205 | |
|
| 0 | 206 | | foreach ((string placeUUID, bool isFavorite) in localFavorites) |
| | 207 | | { |
| 0 | 208 | | if (!isFavorite) |
| | 209 | | continue; |
| | 210 | |
|
| 0 | 211 | | if(placesById.TryGetValue(placeUUID, out var place)) |
| 0 | 212 | | composedFavorites.Add(place); |
| | 213 | | } |
| 0 | 214 | | composedFavoritesDirty = false; |
| | 215 | |
|
| 0 | 216 | | return composedFavorites; |
| 0 | 217 | | } |
| | 218 | |
|
| | 219 | | public async UniTask SetPlaceFavorite(string placeUUID, bool isFavorite, CancellationToken ct) |
| | 220 | | { |
| 0 | 221 | | localFavorites[placeUUID] = isFavorite; |
| 0 | 222 | | composedFavoritesDirty = true; |
| 0 | 223 | | await client.SetPlaceFavorite(placeUUID, isFavorite, ct); |
| 0 | 224 | | } |
| | 225 | |
|
| | 226 | | public async UniTask SetPlaceVote(bool? isUpvote, string placeUUID, CancellationToken ct) |
| | 227 | | { |
| 0 | 228 | | await client.SetPlaceVote(isUpvote, placeUUID, ct); |
| 0 | 229 | | } |
| | 230 | |
|
| | 231 | | public async UniTask SetPlaceFavorite(Vector2Int coords, bool isFavorite, CancellationToken ct) |
| | 232 | | { |
| 0 | 233 | | var place = await GetPlace(coords, ct); |
| 0 | 234 | | await SetPlaceFavorite(place.id, isFavorite, ct); |
| 0 | 235 | | } |
| | 236 | |
|
| | 237 | | public async UniTask<bool> IsFavoritePlace(IHotScenesController.PlaceInfo placeInfo, CancellationToken ct, bool |
| | 238 | | { |
| 0 | 239 | | var favorites = await GetFavorites(-1,-1, ct, renewCache); |
| | 240 | |
|
| 0 | 241 | | foreach (IHotScenesController.PlaceInfo favorite in favorites) |
| | 242 | | { |
| 0 | 243 | | if (favorite.id == placeInfo.id) |
| 0 | 244 | | return true; |
| | 245 | | } |
| | 246 | |
|
| 0 | 247 | | return false; |
| 0 | 248 | | } |
| | 249 | |
|
| | 250 | | public async UniTask<bool> IsFavoritePlace(Vector2Int coords, CancellationToken ct, bool renewCache = false) |
| | 251 | | { |
| | 252 | | // We could call IsFavoritePlace with the placeInfo and avoid code repetition, but this way we can have the |
| 0 | 253 | | (IHotScenesController.PlaceInfo placeInfo, IReadOnlyList<IHotScenesController.PlaceInfo> favorites) = await |
| | 254 | |
|
| 0 | 255 | | foreach (IHotScenesController.PlaceInfo favorite in favorites) |
| | 256 | | { |
| 0 | 257 | | if (favorite.id == placeInfo.id) |
| 0 | 258 | | return true; |
| | 259 | | } |
| | 260 | |
|
| 0 | 261 | | return false; |
| 0 | 262 | | } |
| | 263 | |
|
| | 264 | | public async UniTask<bool> IsFavoritePlace(string placeUUID, CancellationToken ct, bool renewCache = false) |
| | 265 | | { |
| | 266 | | // We could call IsFavoritePlace with the placeInfo and avoid code repetition, but this way we can have the |
| 0 | 267 | | (IHotScenesController.PlaceInfo placeInfo, IReadOnlyList<IHotScenesController.PlaceInfo> favorites) = await |
| | 268 | |
|
| 0 | 269 | | foreach (IHotScenesController.PlaceInfo favorite in favorites) |
| | 270 | | { |
| 0 | 271 | | if (favorite.id == placeInfo.id) |
| 0 | 272 | | return true; |
| | 273 | | } |
| | 274 | |
|
| 0 | 275 | | return false; |
| 0 | 276 | | } |
| | 277 | |
|
| | 278 | | internal void CachePlace(IHotScenesController.PlaceInfo placeInfo) |
| | 279 | | { |
| 0 | 280 | | placesById[placeInfo.id] = placeInfo; |
| | 281 | |
|
| 0 | 282 | | foreach (Vector2Int placeInfoPosition in placeInfo.Positions) { placesByCoords[placeInfoPosition] = placeInf |
| 0 | 283 | | } |
| | 284 | |
|
| | 285 | | public async UniTask<(IHotScenesController.PlacesAPIResponse response, bool success)> CreateRequest(string endPo |
| | 286 | | { |
| 0 | 287 | | var response = await client.GetMostActivePlaces(pageNumber, pageSize,additionalData["filter"],additionalData |
| | 288 | | // Client will handle most of the error handling and throw if needed |
| 0 | 289 | | return (response, true); |
| 0 | 290 | | } |
| | 291 | |
|
| | 292 | | public async UniTask<IReadOnlyList<string>> GetPointsOfInterestCoords(CancellationToken ct, bool renewCache = fa |
| | 293 | | { |
| 1 | 294 | | if (renewCache || pointsOfInterestCoords == null) |
| 3 | 295 | | pointsOfInterestCoords = await client.GetPointsOfInterestCoords(ct); |
| | 296 | |
|
| 1 | 297 | | return pointsOfInterestCoords; |
| 1 | 298 | | } |
| | 299 | |
|
| | 300 | | public async UniTask ReportPlace(PlaceContentReportPayload placeContentReportPayload, CancellationToken ct) => |
| 0 | 301 | | await client.ReportPlace(placeContentReportPayload, ct); |
| | 302 | |
|
| | 303 | | public async UniTask<List<PlaceCategoryInfo>> GetPlaceCategories(CancellationToken ct, bool renewCache = false) |
| | 304 | | { |
| 0 | 305 | | if (renewCache || placeCategories == null) |
| 0 | 306 | | placeCategories = await client.GetPlaceCategories(ct); |
| | 307 | |
|
| 0 | 308 | | return placeCategories; |
| 0 | 309 | | } |
| | 310 | |
|
| | 311 | | public void Dispose() |
| | 312 | | { |
| 425 | 313 | | disposeCts.SafeCancelAndDispose(); |
| 425 | 314 | | } |
| | 315 | | } |
| | 316 | | } |