diff --git a/ArchiSteamFarm/ASF.cs b/ArchiSteamFarm/ASF.cs index 9352b6044..fe08f7060 100644 --- a/ArchiSteamFarm/ASF.cs +++ b/ArchiSteamFarm/ASF.cs @@ -91,7 +91,7 @@ namespace ArchiSteamFarm { GitHub.ReleaseResponse releaseResponse; if (Program.GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) { - WebBrowser.ObjectResponse objectResponse = await Program.WebBrowser.UrlGetToJsonObjectRetry(releaseURL).ConfigureAwait(false); + WebBrowser.ObjectResponse objectResponse = await Program.WebBrowser.UrlGetToJsonObject(releaseURL).ConfigureAwait(false); if (objectResponse?.Content == null) { ArchiLogger.LogGenericWarning(Strings.ErrorUpdateCheckFailed); return null; @@ -99,7 +99,7 @@ namespace ArchiSteamFarm { releaseResponse = objectResponse.Content; } else { - WebBrowser.ObjectResponse> objectResponse = await Program.WebBrowser.UrlGetToJsonObjectRetry>(releaseURL).ConfigureAwait(false); + WebBrowser.ObjectResponse> objectResponse = await Program.WebBrowser.UrlGetToJsonObject>(releaseURL).ConfigureAwait(false); if ((objectResponse?.Content == null) || (objectResponse.Content.Count == 0)) { ArchiLogger.LogGenericWarning(Strings.ErrorUpdateCheckFailed); return null; @@ -154,7 +154,7 @@ namespace ArchiSteamFarm { ArchiLogger.LogGenericInfo(string.Format(Strings.UpdateDownloadingNewVersion, newVersion, binaryAsset.Size / 1024 / 1024)); - WebBrowser.BinaryResponse response = await Program.WebBrowser.UrlGetToBinaryWithProgressRetry(binaryAsset.DownloadURL).ConfigureAwait(false); + WebBrowser.BinaryResponse response = await Program.WebBrowser.UrlGetToBinaryWithProgress(binaryAsset.DownloadURL).ConfigureAwait(false); if (response?.Content == null) { return null; } diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs index 0aeeb8897..da46035c6 100644 --- a/ArchiSteamFarm/ArchiWebHandler.cs +++ b/ArchiSteamFarm/ArchiWebHandler.cs @@ -96,7 +96,7 @@ namespace ArchiSteamFarm { { "tradeofferid", tradeID.ToString() } }; - return await UrlPostRetryWithSession(SteamCommunityURL, request, data, referer).ConfigureAwait(false); + return await UrlPostWithSession(SteamCommunityURL, request, data, referer).ConfigureAwait(false); } internal async Task AddFreeLicense(uint subID) { @@ -113,7 +113,7 @@ namespace ArchiSteamFarm { { "action", "add_to_cart" } }; - HtmlDocument htmlDocument = await UrlPostToHtmlDocumentRetryWithSession(SteamStoreURL, request, data).ConfigureAwait(false); + HtmlDocument htmlDocument = await UrlPostToHtmlDocumentWithSession(SteamStoreURL, request, data).ConfigureAwait(false); return htmlDocument?.DocumentNode.SelectSingleNode("//div[@class='add_free_content_success_area']") != null; } @@ -128,7 +128,7 @@ namespace ArchiSteamFarm { // Extra entry for sessionID Dictionary data = new Dictionary(2) { { "appid_to_clear_from_queue", appID.ToString() } }; - return await UrlPostRetryWithSession(SteamStoreURL, request, data).ConfigureAwait(false); + return await UrlPostWithSession(SteamStoreURL, request, data).ConfigureAwait(false); } internal async Task DeclineTradeOffer(ulong tradeID) { @@ -175,7 +175,7 @@ namespace ArchiSteamFarm { // Extra entry for sessionID Dictionary data = new Dictionary(2) { { "queuetype", "0" } }; - Steam.NewDiscoveryQueueResponse output = await UrlPostToJsonObjectRetryWithSession(SteamStoreURL, request, data).ConfigureAwait(false); + Steam.NewDiscoveryQueueResponse output = await UrlPostToJsonObjectWithSession(SteamStoreURL, request, data).ConfigureAwait(false); return output?.Queue; } @@ -303,7 +303,7 @@ namespace ArchiSteamFarm { } string request = "/my/badges?l=english&p=" + page; - return await UrlGetToHtmlDocumentRetryWithSession(SteamCommunityURL, request).ConfigureAwait(false); + return await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request).ConfigureAwait(false); } internal async Task GetConfirmationDetails(string deviceID, string confirmationHash, uint time, MobileAuthenticator.Confirmation confirmation) { @@ -314,7 +314,7 @@ namespace ArchiSteamFarm { string request = "/mobileconf/details/" + confirmation.ID + "?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf"; - Steam.ConfirmationDetails response = await UrlGetToJsonObjectRetryWithSession(SteamCommunityURL, request).ConfigureAwait(false); + Steam.ConfirmationDetails response = await UrlGetToJsonObjectWithSession(SteamCommunityURL, request).ConfigureAwait(false); if (response?.Success != true) { return null; } @@ -330,17 +330,17 @@ namespace ArchiSteamFarm { } string request = "/mobileconf/conf?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf"; - return await UrlGetToHtmlDocumentRetryWithSession(SteamCommunityURL, request).ConfigureAwait(false); + return await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request).ConfigureAwait(false); } internal async Task GetDiscoveryQueuePage() { const string request = "/explore?l=english"; - return await UrlGetToHtmlDocumentRetryWithSession(SteamStoreURL, request).ConfigureAwait(false); + return await UrlGetToHtmlDocumentWithSession(SteamStoreURL, request).ConfigureAwait(false); } internal async Task> GetFamilySharingSteamIDs() { const string request = "/account/managedevices"; - HtmlDocument htmlDocument = await UrlGetToHtmlDocumentRetryWithSession(SteamStoreURL, request).ConfigureAwait(false); + HtmlDocument htmlDocument = await UrlGetToHtmlDocumentWithSession(SteamStoreURL, request).ConfigureAwait(false); HtmlNodeCollection htmlNodes = htmlDocument?.DocumentNode.SelectNodes("(//table[@class='accountTable'])[last()]//a/@data-miniprofile"); if (htmlNodes == null) { @@ -374,7 +374,7 @@ namespace ArchiSteamFarm { } string request = "/my/gamecards/" + appID + "?l=english"; - return await UrlGetToHtmlDocumentRetryWithSession(SteamCommunityURL, request).ConfigureAwait(false); + return await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request).ConfigureAwait(false); } [SuppressMessage("ReSharper", "FunctionComplexityOverflow")] @@ -394,7 +394,7 @@ namespace ArchiSteamFarm { try { while (true) { - Steam.InventoryResponse response = await UrlGetToJsonObjectRetryWithSession(SteamCommunityURL, request + (startAssetID > 0 ? "&start_assetid=" + startAssetID : "")).ConfigureAwait(false); + Steam.InventoryResponse response = await UrlGetToJsonObjectWithSession(SteamCommunityURL, request + (startAssetID > 0 ? "&start_assetid=" + startAssetID : "")).ConfigureAwait(false); if (response == null) { return null; @@ -488,7 +488,7 @@ namespace ArchiSteamFarm { internal async Task> GetMyOwnedGames() { const string request = "/my/games/?xml=1"; - XmlDocument response = await UrlGetToXmlDocumentRetryWithSession(SteamCommunityURL, request).ConfigureAwait(false); + XmlDocument response = await UrlGetToXmlDocumentWithSession(SteamCommunityURL, request).ConfigureAwait(false); XmlNodeList xmlNodeList = response?.SelectNodes("gamesList/games/game"); if ((xmlNodeList == null) || (xmlNodeList.Count == 0)) { @@ -604,7 +604,7 @@ namespace ArchiSteamFarm { internal async Task GetSteamAwardsPage() { const string request = "/SteamAwards?l=english"; - return await UrlGetToHtmlDocumentRetryWithSession(SteamStoreURL, request).ConfigureAwait(false); + return await UrlGetToHtmlDocumentWithSession(SteamStoreURL, request).ConfigureAwait(false); } internal async Task GetTradeHoldDurationForTrade(ulong tradeID) { @@ -615,7 +615,7 @@ namespace ArchiSteamFarm { string request = "/tradeoffer/" + tradeID + "?l=english"; - HtmlDocument htmlDocument = await UrlGetToHtmlDocumentRetryWithSession(SteamCommunityURL, request).ConfigureAwait(false); + HtmlDocument htmlDocument = await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request).ConfigureAwait(false); HtmlNode htmlNode = htmlDocument?.DocumentNode.SelectSingleNode("//div[@class='pagecontent']/script"); if (htmlNode == null) { @@ -715,7 +715,7 @@ namespace ArchiSteamFarm { } const string request = "/my/tradeoffers/privacy?l=english"; - HtmlDocument htmlDocument = await UrlGetToHtmlDocumentRetryWithSession(SteamCommunityURL, request).ConfigureAwait(false); + HtmlDocument htmlDocument = await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request).ConfigureAwait(false); if (htmlDocument == null) { return null; @@ -760,7 +760,7 @@ namespace ArchiSteamFarm { string request = "/mobileconf/ajaxop?op=" + (accept ? "allow" : "cancel") + "&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf&cid=" + confirmationID + "&ck=" + confirmationKey; - Steam.ConfirmationResponse response = await UrlGetToJsonObjectRetryWithSession(SteamCommunityURL, request).ConfigureAwait(false); + Steam.ConfirmationResponse response = await UrlGetToJsonObjectWithSession(SteamCommunityURL, request).ConfigureAwait(false); return response?.Success; } @@ -788,7 +788,7 @@ namespace ArchiSteamFarm { data.Add(new KeyValuePair("ck[]", confirmation.Key.ToString())); } - Steam.ConfirmationResponse response = await UrlPostToJsonObjectRetryWithSession(SteamCommunityURL, request, data).ConfigureAwait(false); + Steam.ConfirmationResponse response = await UrlPostToJsonObjectWithSession(SteamCommunityURL, request, data).ConfigureAwait(false); return response?.Success; } @@ -919,7 +919,7 @@ namespace ArchiSteamFarm { // Extra entry for sessionID Dictionary data = new Dictionary(2) { { "action", "join" } }; - return await UrlPostRetryWithSession(SteamCommunityURL, request, data, session: ESession.CamelCase).ConfigureAwait(false); + return await UrlPostWithSession(SteamCommunityURL, request, data, session: ESession.CamelCase).ConfigureAwait(false); } internal async Task MarkInventory() { @@ -941,7 +941,7 @@ namespace ArchiSteamFarm { } const string request = "/my/inventory"; - await UrlHeadRetryWithSession(SteamCommunityURL, request).ConfigureAwait(false); + await UrlHeadWithSession(SteamCommunityURL, request).ConfigureAwait(false); } finally { if (Program.GlobalConfig.InventoryLimiterDelay == 0) { InventorySemaphore.Release(); @@ -956,7 +956,7 @@ namespace ArchiSteamFarm { internal async Task MarkSentTrades() { const string request = "/my/tradeoffers/sent"; - return await UrlHeadRetryWithSession(SteamCommunityURL, request).ConfigureAwait(false); + return await UrlHeadWithSession(SteamCommunityURL, request).ConfigureAwait(false); } internal void OnDisconnected() { @@ -976,7 +976,7 @@ namespace ArchiSteamFarm { // Extra entry for sessionID Dictionary data = new Dictionary(2) { { "wallet_code", key } }; - Steam.RedeemWalletResponse response = await UrlPostToJsonObjectRetryWithSession(SteamStoreURL, request, data).ConfigureAwait(false); + Steam.RedeemWalletResponse response = await UrlPostToJsonObjectWithSession(SteamStoreURL, request, data).ConfigureAwait(false); if (response == null) { return null; } @@ -1017,7 +1017,7 @@ namespace ArchiSteamFarm { { "json_tradeoffer", JsonConvert.SerializeObject(trade) }, { "trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : new JObject { { "trade_offer_access_token", token } }.ToString(Formatting.None) } })) { - if (!await UrlPostRetryWithSession(SteamCommunityURL, request, data, referer).ConfigureAwait(false)) { + if (!await UrlPostWithSession(SteamCommunityURL, request, data, referer).ConfigureAwait(false)) { return false; } } @@ -1039,7 +1039,7 @@ namespace ArchiSteamFarm { { "appid", appID.ToString() } }; - return await UrlPostRetryWithSession(SteamStoreURL, request, data).ConfigureAwait(false); + return await UrlPostWithSession(SteamStoreURL, request, data).ConfigureAwait(false); } internal async Task UnpackBooster(uint appID, ulong itemID) { @@ -1056,7 +1056,7 @@ namespace ArchiSteamFarm { { "communityitemid", itemID.ToString() } }; - Steam.GenericResponse response = await UrlPostToJsonObjectRetryWithSession(SteamCommunityURL, request, data).ConfigureAwait(false); + Steam.GenericResponse response = await UrlPostToJsonObjectWithSession(SteamCommunityURL, request, data).ConfigureAwait(false); return response?.Result == EResult.OK; } @@ -1128,7 +1128,7 @@ namespace ArchiSteamFarm { private async Task<(ESteamApiKeyState State, string Key)> GetApiKeyState() { const string request = "/dev/apikey?l=english"; - HtmlDocument htmlDocument = await UrlGetToHtmlDocumentRetryWithSession(SteamCommunityURL, request).ConfigureAwait(false); + HtmlDocument htmlDocument = await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request).ConfigureAwait(false); HtmlNode titleNode = htmlDocument?.DocumentNode.SelectSingleNode("//div[@id='mainContents']/h2"); if (titleNode == null) { @@ -1227,7 +1227,7 @@ namespace ArchiSteamFarm { private async Task IsInventoryPublic() { const string request = "/my/edit/settings?l=english"; - HtmlDocument htmlDocument = await UrlGetToHtmlDocumentRetryWithSession(SteamCommunityURL, request).ConfigureAwait(false); + HtmlDocument htmlDocument = await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request).ConfigureAwait(false); HtmlNode htmlNode = htmlDocument?.DocumentNode.SelectSingleNode("//input[@id='inventoryPrivacySetting_public']"); if (htmlNode == null) { @@ -1341,7 +1341,7 @@ namespace ArchiSteamFarm { { "Submit", "Register" } }; - return await UrlPostRetryWithSession(SteamCommunityURL, request, data).ConfigureAwait(false); + return await UrlPostWithSession(SteamCommunityURL, request, data).ConfigureAwait(false); } private async Task UnlockParentalAccount(string parentalPin) { @@ -1377,11 +1377,11 @@ namespace ArchiSteamFarm { Dictionary data = new Dictionary(1) { { "pin", parentalPin } }; - WebBrowser.BasicResponse response = await WebBrowser.UrlPostRetry(serviceURL + request, data, serviceURL).ConfigureAwait(false); + WebBrowser.BasicResponse response = await WebBrowser.UrlPost(serviceURL + request, data, serviceURL).ConfigureAwait(false); return (response != null) && !IsSessionExpiredUri(response.FinalUri); } - private async Task UrlGetToHtmlDocumentRetryWithSession(string host, string request, byte maxTries = WebBrowser.MaxTries) { + private async Task UrlGetToHtmlDocumentWithSession(string host, string request, byte maxTries = WebBrowser.MaxTries) { if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(request)) { Bot.ArchiLogger.LogNullError(nameof(host) + " || " + nameof(request)); return null; @@ -1407,7 +1407,7 @@ namespace ArchiSteamFarm { return null; } - WebBrowser.HtmlDocumentResponse response = await WebBrowser.UrlGetToHtmlDocumentRetry(host + request).ConfigureAwait(false); + WebBrowser.HtmlDocumentResponse response = await WebBrowser.UrlGetToHtmlDocument(host + request).ConfigureAwait(false); if (response == null) { return null; } @@ -1422,10 +1422,10 @@ namespace ArchiSteamFarm { return null; } - return await UrlGetToHtmlDocumentRetryWithSession(host, request, --maxTries).ConfigureAwait(false); + return await UrlGetToHtmlDocumentWithSession(host, request, --maxTries).ConfigureAwait(false); } - private async Task UrlGetToJsonObjectRetryWithSession(string host, string request, byte maxTries = WebBrowser.MaxTries) { + private async Task UrlGetToJsonObjectWithSession(string host, string request, byte maxTries = WebBrowser.MaxTries) { if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(request)) { Bot.ArchiLogger.LogNullError(nameof(host) + " || " + nameof(request)); return default; @@ -1451,7 +1451,7 @@ namespace ArchiSteamFarm { return default; } - WebBrowser.ObjectResponse response = await WebBrowser.UrlGetToJsonObjectRetry(host + request).ConfigureAwait(false); + WebBrowser.ObjectResponse response = await WebBrowser.UrlGetToJsonObject(host + request).ConfigureAwait(false); if (response == null) { return default; } @@ -1466,10 +1466,10 @@ namespace ArchiSteamFarm { return default; } - return await UrlGetToJsonObjectRetryWithSession(host, request, --maxTries).ConfigureAwait(false); + return await UrlGetToJsonObjectWithSession(host, request, --maxTries).ConfigureAwait(false); } - private async Task UrlGetToXmlDocumentRetryWithSession(string host, string request, byte maxTries = WebBrowser.MaxTries) { + private async Task UrlGetToXmlDocumentWithSession(string host, string request, byte maxTries = WebBrowser.MaxTries) { if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(request)) { Bot.ArchiLogger.LogNullError(nameof(host) + " || " + nameof(request)); return null; @@ -1495,7 +1495,7 @@ namespace ArchiSteamFarm { return null; } - WebBrowser.XmlResponse response = await WebBrowser.UrlGetToXmlRetry(host + request).ConfigureAwait(false); + WebBrowser.XmlResponse response = await WebBrowser.UrlGetToXml(host + request).ConfigureAwait(false); if (response == null) { return null; } @@ -1510,10 +1510,10 @@ namespace ArchiSteamFarm { return null; } - return await UrlGetToXmlDocumentRetryWithSession(host, request, --maxTries).ConfigureAwait(false); + return await UrlGetToXmlDocumentWithSession(host, request, --maxTries).ConfigureAwait(false); } - private async Task UrlHeadRetryWithSession(string host, string request, byte maxTries = WebBrowser.MaxTries) { + private async Task UrlHeadWithSession(string host, string request, byte maxTries = WebBrowser.MaxTries) { if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(request)) { Bot.ArchiLogger.LogNullError(nameof(host) + " || " + nameof(request)); return false; @@ -1539,7 +1539,7 @@ namespace ArchiSteamFarm { return false; } - WebBrowser.BasicResponse response = await WebBrowser.UrlHeadRetry(host + request).ConfigureAwait(false); + WebBrowser.BasicResponse response = await WebBrowser.UrlHead(host + request).ConfigureAwait(false); if (response == null) { return false; } @@ -1554,83 +1554,10 @@ namespace ArchiSteamFarm { return false; } - return await UrlHeadRetryWithSession(host, request, --maxTries).ConfigureAwait(false); + return await UrlHeadWithSession(host, request, --maxTries).ConfigureAwait(false); } - private async Task UrlPostRetryWithSession(string host, string request, Dictionary data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) { - if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(request)) { - Bot.ArchiLogger.LogNullError(nameof(host) + " || " + nameof(request)); - return false; - } - - if (maxTries == 0) { - Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)); - Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); - return false; - } - - // If session refresh is already in progress, wait for it - await SessionSemaphore.WaitAsync().ConfigureAwait(false); - SessionSemaphore.Release(); - - for (byte i = 0; (i < Program.GlobalConfig.ConnectionTimeout) && (SteamID == 0) && Bot.IsConnectedAndLoggedOn; i++) { - await Task.Delay(1000).ConfigureAwait(false); - } - - if (SteamID == 0) { - Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); - Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); - return false; - } - - if (session != ESession.None) { - string sessionID = WebBrowser.CookieContainer.GetCookieValue(host, "sessionid"); - - if (string.IsNullOrEmpty(sessionID)) { - Bot.ArchiLogger.LogNullError(nameof(sessionID)); - return false; - } - - string sessionName; - - switch (session) { - case ESession.CamelCase: - sessionName = "sessionID"; - break; - case ESession.Lowercase: - sessionName = "sessionid"; - break; - default: - Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(session), session)); - return false; - } - - if (data != null) { - data[sessionName] = sessionID; - } else { - data = new Dictionary(1) { { sessionName, sessionID } }; - } - } - - WebBrowser.BasicResponse response = await WebBrowser.UrlPostRetry(host + request, data, referer).ConfigureAwait(false); - if (response == null) { - return false; - } - - if (!IsSessionExpiredUri(response.FinalUri)) { - return true; - } - - if (!await RefreshSession(host).ConfigureAwait(false)) { - Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); - Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); - return false; - } - - return await UrlPostRetryWithSession(host, request, data, referer, session, --maxTries).ConfigureAwait(false); - } - - private async Task UrlPostToHtmlDocumentRetryWithSession(string host, string request, Dictionary data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) { + private async Task UrlPostToHtmlDocumentWithSession(string host, string request, Dictionary data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) { if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(request)) { Bot.ArchiLogger.LogNullError(nameof(host) + " || " + nameof(request)); return null; @@ -1685,7 +1612,7 @@ namespace ArchiSteamFarm { } } - WebBrowser.HtmlDocumentResponse response = await WebBrowser.UrlPostToHtmlDocumentRetry(host + request, data, referer).ConfigureAwait(false); + WebBrowser.HtmlDocumentResponse response = await WebBrowser.UrlPostToHtmlDocument(host + request, data, referer).ConfigureAwait(false); if (response == null) { return null; } @@ -1700,10 +1627,10 @@ namespace ArchiSteamFarm { return null; } - return await UrlPostToHtmlDocumentRetryWithSession(host, request, data, referer, session, --maxTries).ConfigureAwait(false); + return await UrlPostToHtmlDocumentWithSession(host, request, data, referer, session, --maxTries).ConfigureAwait(false); } - private async Task UrlPostToJsonObjectRetryWithSession(string host, string request, Dictionary data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) { + private async Task UrlPostToJsonObjectWithSession(string host, string request, Dictionary data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) { if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(request)) { Bot.ArchiLogger.LogNullError(nameof(host) + " || " + nameof(request)); return default; @@ -1758,7 +1685,7 @@ namespace ArchiSteamFarm { } } - WebBrowser.ObjectResponse response = await WebBrowser.UrlPostToJsonObjectRetry(host + request, data, referer).ConfigureAwait(false); + WebBrowser.ObjectResponse response = await WebBrowser.UrlPostToJsonObject(host + request, data, referer).ConfigureAwait(false); if (response == null) { return default; } @@ -1773,10 +1700,10 @@ namespace ArchiSteamFarm { return default; } - return await UrlPostToJsonObjectRetryWithSession(host, request, data, referer, session, --maxTries).ConfigureAwait(false); + return await UrlPostToJsonObjectWithSession(host, request, data, referer, session, --maxTries).ConfigureAwait(false); } - private async Task UrlPostToJsonObjectRetryWithSession(string host, string request, List> data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) { + private async Task UrlPostToJsonObjectWithSession(string host, string request, List> data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) { if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(request)) { Bot.ArchiLogger.LogNullError(nameof(host) + " || " + nameof(request)); return default; @@ -1834,7 +1761,7 @@ namespace ArchiSteamFarm { } } - WebBrowser.ObjectResponse response = await WebBrowser.UrlPostToJsonObjectRetry(host + request, data, referer).ConfigureAwait(false); + WebBrowser.ObjectResponse response = await WebBrowser.UrlPostToJsonObject(host + request, data, referer).ConfigureAwait(false); if (response == null) { return default; } @@ -1849,7 +1776,80 @@ namespace ArchiSteamFarm { return default; } - return await UrlPostToJsonObjectRetryWithSession(host, request, data, referer, session, --maxTries).ConfigureAwait(false); + return await UrlPostToJsonObjectWithSession(host, request, data, referer, session, --maxTries).ConfigureAwait(false); + } + + private async Task UrlPostWithSession(string host, string request, Dictionary data = null, string referer = null, ESession session = ESession.Lowercase, byte maxTries = WebBrowser.MaxTries) { + if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(request)) { + Bot.ArchiLogger.LogNullError(nameof(host) + " || " + nameof(request)); + return false; + } + + if (maxTries == 0) { + Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)); + Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); + return false; + } + + // If session refresh is already in progress, wait for it + await SessionSemaphore.WaitAsync().ConfigureAwait(false); + SessionSemaphore.Release(); + + for (byte i = 0; (i < Program.GlobalConfig.ConnectionTimeout) && (SteamID == 0) && Bot.IsConnectedAndLoggedOn; i++) { + await Task.Delay(1000).ConfigureAwait(false); + } + + if (SteamID == 0) { + Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); + Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); + return false; + } + + if (session != ESession.None) { + string sessionID = WebBrowser.CookieContainer.GetCookieValue(host, "sessionid"); + + if (string.IsNullOrEmpty(sessionID)) { + Bot.ArchiLogger.LogNullError(nameof(sessionID)); + return false; + } + + string sessionName; + + switch (session) { + case ESession.CamelCase: + sessionName = "sessionID"; + break; + case ESession.Lowercase: + sessionName = "sessionid"; + break; + default: + Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(session), session)); + return false; + } + + if (data != null) { + data[sessionName] = sessionID; + } else { + data = new Dictionary(1) { { sessionName, sessionID } }; + } + } + + WebBrowser.BasicResponse response = await WebBrowser.UrlPost(host + request, data, referer).ConfigureAwait(false); + if (response == null) { + return false; + } + + if (!IsSessionExpiredUri(response.FinalUri)) { + return true; + } + + if (!await RefreshSession(host).ConfigureAwait(false)) { + Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); + Bot.ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, host + request)); + return false; + } + + return await UrlPostWithSession(host, request, data, referer, session, --maxTries).ConfigureAwait(false); } private enum ESession : byte { diff --git a/ArchiSteamFarm/Statistics.cs b/ArchiSteamFarm/Statistics.cs index 52a6ed13b..a4411e3f2 100644 --- a/ArchiSteamFarm/Statistics.cs +++ b/ArchiSteamFarm/Statistics.cs @@ -72,7 +72,7 @@ namespace ArchiSteamFarm { }; // We don't need retry logic here - if (await Program.WebBrowser.UrlPost(request, data).ConfigureAwait(false) != null) { + if (await Program.WebBrowser.UrlPost(request, data, maxTries: 1).ConfigureAwait(false) != null) { LastHeartBeat = DateTime.UtcNow; } } finally { @@ -131,7 +131,7 @@ namespace ArchiSteamFarm { }; // We don't need retry logic here - if (await Program.WebBrowser.UrlPost(request, data).ConfigureAwait(false) != null) { + if (await Program.WebBrowser.UrlPost(request, data, maxTries: 1).ConfigureAwait(false) != null) { LastAnnouncementCheck = DateTime.UtcNow; ShouldSendHeartBeats = true; } diff --git a/ArchiSteamFarm/WebBrowser.cs b/ArchiSteamFarm/WebBrowser.cs index 8b1030934..0a22c8bb0 100644 --- a/ArchiSteamFarm/WebBrowser.cs +++ b/ArchiSteamFarm/WebBrowser.cs @@ -90,45 +90,87 @@ namespace ArchiSteamFarm { return htmlDocument; } - internal async Task UrlGetToBinaryWithProgressRetry(string request, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); + internal async Task UrlGetToBinaryWithProgress(string request, string referer = null, byte maxTries = MaxTries) { + if (string.IsNullOrEmpty(request) || (maxTries == 0)) { + ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries)); return null; } - BinaryResponse response = null; + for (byte i = 0; i < MaxTries; i++) { + const byte printPercentage = 10; + const byte maxBatches = 99 / printPercentage; - for (byte i = 0; (i < MaxTries) && (response == null); i++) { - response = await UrlGetToBinaryWithProgress(request, referer).ConfigureAwait(false); + using (HttpResponseMessage response = await InternalGet(request, referer, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false)) { + if (response == null) { + continue; + } + + ArchiLogger.LogGenericDebug("0%..."); + + uint contentLength = (uint) response.Content.Headers.ContentLength.GetValueOrDefault(); + + using (MemoryStream ms = new MemoryStream((int) contentLength)) { + try { + using (Stream contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) { + byte batch = 0; + uint readThisBatch = 0; + byte[] buffer = new byte[8192]; // This is HttpClient's buffer, using more doesn't make sense + + while (contentStream.CanRead) { + int read = await contentStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); + if (read == 0) { + break; + } + + await ms.WriteAsync(buffer, 0, read).ConfigureAwait(false); + + if ((contentLength == 0) || (batch >= maxBatches)) { + continue; + } + + readThisBatch += (uint) read; + + if (readThisBatch < contentLength / printPercentage) { + continue; + } + + readThisBatch -= contentLength / printPercentage; + ArchiLogger.LogGenericDebug(++batch * printPercentage + "%..."); + } + } + } catch (Exception e) { + ArchiLogger.LogGenericDebuggingException(e); + return null; + } + + ArchiLogger.LogGenericDebug("100%"); + return new BinaryResponse(response, ms.ToArray()); + } + } } - if (response == null) { - ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxTries)); - ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); - return null; - } - - return response; + ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxTries)); + ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); + return null; } - internal async Task UrlGetToHtmlDocumentRetry(string request, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); + internal async Task UrlGetToHtmlDocument(string request, string referer = null, byte maxTries = MaxTries) { + if (string.IsNullOrEmpty(request) || (maxTries == 0)) { + ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries)); return null; } - StringResponse response = await UrlGetToStringRetry(request, referer).ConfigureAwait(false); + StringResponse response = await UrlGetToString(request, referer, maxTries).ConfigureAwait(false); return response != null ? new HtmlDocumentResponse(response) : null; } - internal async Task> UrlGetToJsonObjectRetry(string request, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); + internal async Task> UrlGetToJsonObject(string request, string referer = null, byte maxTries = MaxTries) { + if (string.IsNullOrEmpty(request) || (maxTries == 0)) { + ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries)); return null; } - StringResponse response = await UrlGetToStringRetry(request, referer).ConfigureAwait(false); - + StringResponse response = await UrlGetToString(request, referer, maxTries).ConfigureAwait(false); if (string.IsNullOrEmpty(response?.Content)) { return null; } @@ -150,14 +192,13 @@ namespace ArchiSteamFarm { return new ObjectResponse(response, obj); } - internal async Task UrlGetToXmlRetry(string request, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); + internal async Task UrlGetToXml(string request, string referer = null, byte maxTries = MaxTries) { + if (string.IsNullOrEmpty(request) || (maxTries == 0)) { + ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries)); return null; } - StringResponse response = await UrlGetToStringRetry(request, referer).ConfigureAwait(false); - + StringResponse response = await UrlGetToString(request, referer, maxTries).ConfigureAwait(false); if (string.IsNullOrEmpty(response?.Content)) { return null; } @@ -174,77 +215,65 @@ namespace ArchiSteamFarm { return new XmlResponse(response, xmlDocument); } - internal async Task UrlHeadRetry(string request, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); + internal async Task UrlHead(string request, string referer = null, byte maxTries = MaxTries) { + if (string.IsNullOrEmpty(request) || (maxTries == 0)) { + ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries)); return null; } - BasicResponse response = null; + for (byte i = 0; i < maxTries; i++) { + using (HttpResponseMessage response = await InternalHead(request, referer).ConfigureAwait(false)) { + if (response == null) { + continue; + } - for (byte i = 0; (i < MaxTries) && (response == null); i++) { - response = await UrlHead(request, referer).ConfigureAwait(false); + return new BasicResponse(response); + } } - if (response == null) { - ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxTries)); - ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); - return null; - } - - return response; + ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, maxTries)); + ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); + return null; } - internal async Task UrlPost(string request, IReadOnlyCollection> data = null, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); + internal async Task UrlPost(string request, IReadOnlyCollection> data = null, string referer = null, byte maxTries = MaxTries) { + if (string.IsNullOrEmpty(request) || (maxTries == 0)) { + ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries)); return null; } - using (HttpResponseMessage response = await UrlPostToHttp(request, data, referer).ConfigureAwait(false)) { - return response != null ? new BasicResponse(response) : null; + for (byte i = 0; i < maxTries; i++) { + using (HttpResponseMessage response = await InternalPost(request, data, referer).ConfigureAwait(false)) { + if (response == null) { + continue; + } + + return new BasicResponse(response); + } } + + ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, maxTries)); + ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); + return null; } - internal async Task UrlPostRetry(string request, IReadOnlyCollection> data = null, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); + internal async Task UrlPostToHtmlDocument(string request, IReadOnlyCollection> data = null, string referer = null, byte maxTries = MaxTries) { + if (string.IsNullOrEmpty(request) || (maxTries == 0)) { + ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries)); return null; } - BasicResponse response = null; - - for (byte i = 0; (i < MaxTries) && (response == null); i++) { - response = await UrlPost(request, data, referer).ConfigureAwait(false); - } - - if (response == null) { - ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxTries)); - ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); - return null; - } - - return response; - } - - internal async Task UrlPostToHtmlDocumentRetry(string request, IReadOnlyCollection> data = null, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); - return null; - } - - StringResponse response = await UrlPostToStringRetry(request, data, referer).ConfigureAwait(false); + StringResponse response = await UrlPostToString(request, data, referer, maxTries).ConfigureAwait(false); return response != null ? new HtmlDocumentResponse(response) : null; } - internal async Task> UrlPostToJsonObjectRetry(string request, IReadOnlyCollection> data = null, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); + internal async Task> UrlPostToJsonObject(string request, IReadOnlyCollection> data = null, string referer = null, byte maxTries = MaxTries) { + if (string.IsNullOrEmpty(request) || (maxTries == 0)) { + ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries)); return null; } - StringResponse response = await UrlPostToStringRetry(request, data, referer).ConfigureAwait(false); - + StringResponse response = await UrlPostToString(request, data, referer, maxTries).ConfigureAwait(false); if (string.IsNullOrEmpty(response?.Content)) { return null; } @@ -266,167 +295,34 @@ namespace ArchiSteamFarm { return new ObjectResponse(response, obj); } - private async Task UrlGetToBinaryWithProgress(string request, string referer = null) { + private async Task InternalGet(string request, string referer = null, HttpCompletionOption httpCompletionOptions = HttpCompletionOption.ResponseContentRead) { if (string.IsNullOrEmpty(request)) { ArchiLogger.LogNullError(nameof(request)); return null; } - const byte printPercentage = 10; - const byte maxBatches = 99 / printPercentage; - - using (HttpResponseMessage response = await UrlGetToHttp(request, referer, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false)) { - if (response == null) { - return null; - } - - ArchiLogger.LogGenericDebug("0%..."); - - uint contentLength = (uint) response.Content.Headers.ContentLength.GetValueOrDefault(); - - using (MemoryStream ms = new MemoryStream((int) contentLength)) { - try { - using (Stream contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) { - byte batch = 0; - uint readThisBatch = 0; - byte[] buffer = new byte[8192]; // This is HttpClient's buffer, using more doesn't make sense - - while (contentStream.CanRead) { - int read = await contentStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); - if (read == 0) { - break; - } - - await ms.WriteAsync(buffer, 0, read).ConfigureAwait(false); - - if ((contentLength == 0) || (batch >= maxBatches)) { - continue; - } - - readThisBatch += (uint) read; - - if (readThisBatch < contentLength / printPercentage) { - continue; - } - - readThisBatch -= contentLength / printPercentage; - ArchiLogger.LogGenericDebug(++batch * printPercentage + "%..."); - } - } - } catch (Exception e) { - ArchiLogger.LogGenericDebuggingException(e); - return null; - } - - ArchiLogger.LogGenericDebug("100%"); - return new BinaryResponse(response, ms.ToArray()); - } - } + return await InternalRequest(new Uri(request), HttpMethod.Get, null, referer, httpCompletionOptions).ConfigureAwait(false); } - private async Task UrlGetToHttp(string request, string referer = null, HttpCompletionOption httpCompletionOptions = HttpCompletionOption.ResponseContentRead) { + private async Task InternalHead(string request, string referer = null) { if (string.IsNullOrEmpty(request)) { ArchiLogger.LogNullError(nameof(request)); return null; } - return await UrlRequest(new Uri(request), HttpMethod.Get, null, referer, httpCompletionOptions).ConfigureAwait(false); + return await InternalRequest(new Uri(request), HttpMethod.Head, null, referer).ConfigureAwait(false); } - private async Task UrlGetToString(string request, string referer = null) { + private async Task InternalPost(string request, IReadOnlyCollection> data = null, string referer = null) { if (string.IsNullOrEmpty(request)) { ArchiLogger.LogNullError(nameof(request)); return null; } - using (HttpResponseMessage response = await UrlGetToHttp(request, referer).ConfigureAwait(false)) { - return response != null ? new StringResponse(response, await response.Content.ReadAsStringAsync().ConfigureAwait(false)) : null; - } + return await InternalRequest(new Uri(request), HttpMethod.Post, data, referer).ConfigureAwait(false); } - private async Task UrlGetToStringRetry(string request, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); - return null; - } - - StringResponse response = null; - - for (byte i = 0; (i < MaxTries) && (response == null); i++) { - response = await UrlGetToString(request, referer).ConfigureAwait(false); - } - - if (response == null) { - ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxTries)); - ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); - return null; - } - - return response; - } - - private async Task UrlHead(string request, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); - return null; - } - - using (HttpResponseMessage response = await UrlHeadToHttp(request, referer).ConfigureAwait(false)) { - return response != null ? new BasicResponse(response) : null; - } - } - - private async Task UrlHeadToHttp(string request, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); - return null; - } - - return await UrlRequest(new Uri(request), HttpMethod.Head, null, referer).ConfigureAwait(false); - } - - private async Task UrlPostToHttp(string request, IReadOnlyCollection> data = null, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); - return null; - } - - return await UrlRequest(new Uri(request), HttpMethod.Post, data, referer).ConfigureAwait(false); - } - - private async Task UrlPostToString(string request, IReadOnlyCollection> data = null, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); - return null; - } - - using (HttpResponseMessage response = await UrlPostToHttp(request, data, referer).ConfigureAwait(false)) { - return response != null ? new StringResponse(response, await response.Content.ReadAsStringAsync().ConfigureAwait(false)) : null; - } - } - - private async Task UrlPostToStringRetry(string request, IReadOnlyCollection> data = null, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); - return null; - } - - StringResponse response = null; - - for (byte i = 0; (i < MaxTries) && (response == null); i++) { - response = await UrlPostToString(request, data, referer).ConfigureAwait(false); - } - - if (response == null) { - ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxTries)); - ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); - return null; - } - - return response; - } - - private async Task UrlRequest(Uri requestUri, HttpMethod httpMethod, IReadOnlyCollection> data = null, string referer = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead, byte maxRedirections = MaxTries) { + private async Task InternalRequest(Uri requestUri, HttpMethod httpMethod, IReadOnlyCollection> data = null, string referer = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead, byte maxRedirections = MaxTries) { if ((requestUri == null) || (httpMethod == null)) { ArchiLogger.LogNullError(nameof(requestUri) + " || " + nameof(httpMethod)); return null; @@ -486,7 +382,7 @@ namespace ArchiSteamFarm { } response.Dispose(); - return await UrlRequest(redirectUri, httpMethod, data, referer, httpCompletionOption, --maxRedirections).ConfigureAwait(false); + return await InternalRequest(redirectUri, httpMethod, data, referer, httpCompletionOption, --maxRedirections).ConfigureAwait(false); } using (response) { @@ -500,6 +396,48 @@ namespace ArchiSteamFarm { } } + private async Task UrlGetToString(string request, string referer = null, byte maxTries = MaxTries) { + if (string.IsNullOrEmpty(request) || (maxTries == 0)) { + ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries)); + return null; + } + + for (byte i = 0; i < maxTries; i++) { + using (HttpResponseMessage response = await InternalGet(request, referer).ConfigureAwait(false)) { + if (response == null) { + continue; + } + + return new StringResponse(response, await response.Content.ReadAsStringAsync().ConfigureAwait(false)); + } + } + + ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, maxTries)); + ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); + return null; + } + + private async Task UrlPostToString(string request, IReadOnlyCollection> data = null, string referer = null, byte maxTries = MaxTries) { + if (string.IsNullOrEmpty(request) || (maxTries == 0)) { + ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries)); + return null; + } + + for (byte i = 0; i < maxTries; i++) { + using (HttpResponseMessage response = await InternalPost(request, data, referer).ConfigureAwait(false)) { + if (response == null) { + continue; + } + + return new StringResponse(response, await response.Content.ReadAsStringAsync().ConfigureAwait(false)); + } + } + + ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, maxTries)); + ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); + return null; + } + internal class BasicResponse { internal readonly Uri FinalUri;