public void Start(PurchasesQueryArgs queryArgs) { SetQueryArgs(queryArgs); m_IsInProgress = true; m_Timestamp = DateTime.Now.Ticks; if (!m_UnityConnect.isUserLoggedIn) { OnOperationError(new UIError(UIErrorCode.AssetStoreOperationError, k_UserNotLoggedInErrorMessage)); return; } m_Result = new AssetStorePurchases(m_OriginalQueryArgs); if ((m_DownloadedAssetsOnly || m_UpdateAvailableOnly) && !m_AdjustedQueryArgs.productIds.Any()) { m_Result.total = 0; onOperationSuccess?.Invoke(this); FinalizedOperation(); return; } // We need to keep a local version of the current timestamp to make sure the callback timestamp is still the original one. var localTimestamp = m_Timestamp; m_AssetStoreRestAPI.GetPurchases(QueryToString(m_AdjustedQueryArgs), (result) => GetPurchasesCallback(result, localTimestamp), OnOperationError); }
public static string QueryToString(PurchasesQueryArgs queryArgs) { var stringBuilder = new StringBuilder($"?offset={queryArgs.startIndex}&limit={queryArgs.limit}", 512); var status = queryArgs.status; if (!string.IsNullOrEmpty(status)) { stringBuilder.Append($"&status={status}"); } if (!string.IsNullOrEmpty(queryArgs.searchText)) { stringBuilder.Append($"&query={Uri.EscapeDataString(queryArgs.searchText)}"); } if (!string.IsNullOrEmpty(queryArgs.orderBy)) { stringBuilder.Append($"&orderBy={queryArgs.orderBy}"); stringBuilder.Append(queryArgs.isReverseOrder ? "&order=desc" : "&order=asc"); } if (queryArgs.labels?.Any() ?? false) { stringBuilder.Append($"&tagging={string.Join(",", queryArgs.labels.Select(label => Uri.EscapeDataString(label)).ToArray())}"); } if (queryArgs.categories?.Any() ?? false) { stringBuilder.Append($"&categories={string.Join(",", queryArgs.categories.Select(cat => Uri.EscapeDataString(cat)).ToArray())}"); } if (queryArgs.productIds?.Any() ?? false) { stringBuilder.Append($"&ids={string.Join(",", queryArgs.productIds.ToArray())}"); } return(stringBuilder.ToString()); }
private void StartFetchOperation(long productId, bool hidden = false) { // when the purchase info is not available for a package (either it's not fetched yet or just not available altogether) // we'll try to fetch the purchase info first and then call the `FetchInternal`. // In the case where a package not purchased, `purchaseInfo` will still be null, // but the generated `AssetStorePackage` in the end will contain an error. var fetchOperation = new AssetStoreListOperation(m_UnityConnect, m_AssetStoreRestAPI, m_AssetStoreCache); var queryArgs = new PurchasesQueryArgs { productIds = new List <string> { productId.ToString() }, status = hidden? "hidden" : string.Empty }; fetchOperation.onOperationSuccess += op => { var purchaseInfo = fetchOperation.result.list.FirstOrDefault(); // If we can't find the purchase info the first time, it could be be that the asset is hidden // we'll do another check if (!hidden && purchaseInfo == null) { StartFetchOperation(productId, true); return; } if (purchaseInfo != null) { m_AssetStoreCache.SetPurchaseInfo(purchaseInfo); } FetchInternal(productId, purchaseInfo); }; fetchOperation.Start(queryArgs); }
public virtual void ListPurchases(PurchasesQueryArgs queryArgs) { CancelListPurchases(); RefreshLocalInfos(); m_ListOperation = m_ListOperation ?? new AssetStoreListOperation(m_UnityConnect, m_AssetStoreRestAPI, m_AssetStoreCache); m_ListOperation.onOperationSuccess += op => { var result = m_ListOperation.result; if (result.list.Count > 0) { var updatedPackages = new List <IPackage>(); foreach (var purchaseInfo in result.list) { var productIdString = purchaseInfo.productId.ToString(); var oldPurchaseInfo = m_AssetStoreCache.GetPurchaseInfo(productIdString); m_AssetStoreCache.SetPurchaseInfo(purchaseInfo); // create a placeholder before fetching data from the cloud for the first time var productInfo = m_AssetStoreCache.GetProductInfo(productIdString); if (productInfo == null) { updatedPackages.Add(new PlaceholderPackage(productIdString, purchaseInfo.displayName, PackageType.AssetStore, PackageTag.Downloadable, PackageProgress.Refreshing)); } else if (oldPurchaseInfo != null) { // for now, `tags` is the only component in `purchase info` that can be updated over time, so we only check for changes there var oldTags = oldPurchaseInfo.tags ?? Enumerable.Empty <string>(); var newTags = purchaseInfo.tags ?? Enumerable.Empty <string>(); if (!oldTags.SequenceEqual(newTags)) { updatedPackages.Add(new AssetStorePackage(m_AssetStoreUtils, m_IOProxy, purchaseInfo, productInfo, m_AssetStoreCache.GetLocalInfo(productIdString))); } } } if (updatedPackages.Any()) { onPackagesChanged?.Invoke(updatedPackages); } } foreach (var cat in result.categories) { m_AssetStoreCache.SetCategory(cat.name, cat.count); } onProductListFetched?.Invoke(result); }; onListOperation?.Invoke(m_ListOperation); m_ListOperation.Start(queryArgs); }
private void SetQueryArgs(PurchasesQueryArgs queryArgs) { m_OriginalQueryArgs = queryArgs; m_DownloadedAssetsOnly = m_OriginalQueryArgs.downloadedOnly; m_UpdateAvailableOnly = m_OriginalQueryArgs.updateAvailableOnly; // The GetPurchases API has a limit of maximum 1000 items (to avoid performance issues) // therefore we do some adjustments to the original query args enforce that limit and split // the original query to multiple batches. We make a clone before when adjusting is needed m_AdjustedQueryArgs = m_OriginalQueryArgs.Clone(); m_AdjustedQueryArgs.limit = Math.Min(m_OriginalQueryArgs.limit, k_QueryLimit); if (m_DownloadedAssetsOnly) { m_AdjustedQueryArgs.status = string.Empty; m_AdjustedQueryArgs.productIds = m_AssetStoreCache.localInfos.Select(info => info.id).ToList(); } else if (m_UpdateAvailableOnly) { m_AdjustedQueryArgs.status = string.Empty; m_AdjustedQueryArgs.productIds = m_AssetStoreCache.localInfos.Where(info => info.canUpdate).Select(info => info.id).ToList(); } }
public AssetStorePurchases(PurchasesQueryArgs queryArgs = null) { this.queryArgs = queryArgs ?? new PurchasesQueryArgs(); }
public virtual void Refresh(RefreshOptions options) { if ((options & RefreshOptions.UpmAny) != 0) { var entitlements = m_PackageDatabase.allPackages.Where(package => package.hasEntitlementsError); if (entitlements.Any()) { foreach (var package in entitlements) { package.ClearErrors(error => error.errorCode == UIErrorCode.Forbidden); } TriggerOnSelectionChanged(); } } if ((options & RefreshOptions.UpmSearch) != 0) { m_UpmClient.SearchAll(); // Since the SearchAll online call now might return error and an empty list, we want to trigger a `SearchOffline` call if // we detect that SearchOffline has not been called before. That way we will have some offline result to show to the user instead of nothing if (!m_RefreshTimestamps.TryGetValue(RefreshOptions.UpmSearchOffline, out var value) || value == 0) { options |= RefreshOptions.UpmSearchOffline; } } if ((options & RefreshOptions.UpmSearchOffline) != 0) { m_UpmClient.SearchAll(true); } if ((options & RefreshOptions.UpmList) != 0) { m_UpmClient.List(); // Do the same logic for the List operations as the Search operations if (!m_RefreshTimestamps.TryGetValue(RefreshOptions.UpmListOffline, out var value) || value == 0) { options |= RefreshOptions.UpmListOffline; } } if ((options & RefreshOptions.UpmListOffline) != 0) { m_UpmClient.List(true); } if ((options & RefreshOptions.Purchased) != 0) { var queryArgs = new PurchasesQueryArgs { startIndex = 0, limit = Math.Max(GetCurrentPage().numCurrentItems, m_PackageManagerPrefs.numItemsPerPage ?? k_DefaultPageSize), searchText = m_PackageFiltering.currentSearchText }; IPage page; if (m_Pages.TryGetValue(PackageFilterTab.AssetStore, out page)) { queryArgs.status = page.filters.status; queryArgs.categories = page.filters.categories; queryArgs.labels = page.filters.labels; queryArgs.orderBy = page.filters.orderBy; queryArgs.isReverseOrder = page.filters.isReverseOrder; } m_AssetStoreClient.ListPurchases(queryArgs); } if ((options & RefreshOptions.PurchasedOffline) != 0) { m_AssetStoreClient.RefreshLocal(); } }