diff --git a/ArchiSteamFarm/ArchiSteamFarm.csproj b/ArchiSteamFarm/ArchiSteamFarm.csproj index 3e3f968ce..3c69c965a 100644 --- a/ArchiSteamFarm/ArchiSteamFarm.csproj +++ b/ArchiSteamFarm/ArchiSteamFarm.csproj @@ -12,6 +12,7 @@ v4.5.1 512 false + publish\ true Disk @@ -26,7 +27,6 @@ 1.0.0.%2a false true - AnyCPU @@ -108,6 +108,7 @@ + diff --git a/ArchiSteamFarm/JSON/GitHub.cs b/ArchiSteamFarm/JSON/GitHub.cs new file mode 100644 index 000000000..e77d78c80 --- /dev/null +++ b/ArchiSteamFarm/JSON/GitHub.cs @@ -0,0 +1,46 @@ +/* + _ _ _ ____ _ _____ + / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ + / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ + / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| + + Copyright 2015-2016 Łukasz "JustArchi" Domeradzki + Contact: JustArchi@JustArchi.net + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace ArchiSteamFarm { + internal static class GitHub { + internal sealed class Asset { + [JsonProperty(PropertyName = "name", Required = Required.Always)] + internal string Name { get; private set; } + + [JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)] + internal string DownloadURL { get; private set; } + } + + internal sealed class ReleaseResponse { + [JsonProperty(PropertyName = "tag_name", Required = Required.Always)] + internal string Tag { get; private set; } + + [JsonProperty(PropertyName = "assets", Required = Required.Always)] + internal List Assets { get; private set; } + } + } +} diff --git a/ArchiSteamFarm/Program.cs b/ArchiSteamFarm/Program.cs index 5c81c5e37..0342d5fef 100644 --- a/ArchiSteamFarm/Program.cs +++ b/ArchiSteamFarm/Program.cs @@ -22,8 +22,10 @@ */ -using Newtonsoft.Json.Linq; +using Newtonsoft.Json; using System; +using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Reflection; using System.Threading; @@ -48,7 +50,7 @@ namespace ArchiSteamFarm { Server // Normal + WCF server } - private const string LatestGithubReleaseURL = "https://api.github.com/repos/JustArchi/ArchiSteamFarm/releases/latest"; + private const string GithubReleaseURL = "https://api.github.com/repos/JustArchi/ArchiSteamFarm/releases"; internal const string ASF = "ASF"; internal const string ConfigDirectory = "config"; @@ -62,6 +64,7 @@ namespace ArchiSteamFarm { private static readonly ManualResetEvent ShutdownResetEvent = new ManualResetEvent(false); private static readonly Assembly Assembly = Assembly.GetExecutingAssembly(); private static readonly string ExecutableFile = Assembly.Location; + private static readonly string ExecutableName = Path.GetFileName(ExecutableFile); private static readonly string ExecutableDirectory = Path.GetDirectoryName(ExecutableFile); private static readonly WCF WCF = new WCF(); @@ -74,29 +77,161 @@ namespace ArchiSteamFarm { private static EMode Mode = EMode.Normal; private static async Task CheckForUpdate() { - JObject response = await WebBrowser.UrlGetToJObject(LatestGithubReleaseURL).ConfigureAwait(false); - if (response == null) { + string oldExeFile = ExecutableFile + ".old"; + + // We booted successfully so we can now remove old exe file + if (File.Exists(oldExeFile)) { + try { + File.Delete(oldExeFile); + } catch (Exception e) { + Logging.LogGenericException(e); + return; + } + } + + if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Unknown) { return; } - string remoteVersion = response["tag_name"].ToString(); - if (string.IsNullOrEmpty(remoteVersion)) { + string releaseURL = GithubReleaseURL; + if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) { + releaseURL += "/latest"; + } + + string response = null; + Logging.LogGenericInfo("Checking new version..."); + for (byte i = 0; i < WebBrowser.MaxRetries && string.IsNullOrEmpty(response); i++) { + response = await WebBrowser.UrlGetToContent(releaseURL).ConfigureAwait(false); + } + + if (string.IsNullOrEmpty(response)) { + Logging.LogGenericWarning("Could not check latest version!"); return; } - string localVersion = Version; + GitHub.ReleaseResponse releaseResponse = null; + if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) { + try { + releaseResponse = JsonConvert.DeserializeObject(response); + } catch (JsonException e) { + Logging.LogGenericException(e); + return; + } + } else { + List releases; + try { + releases = JsonConvert.DeserializeObject>(response); + } catch (JsonException e) { + Logging.LogGenericException(e); + return; + } - Logging.LogGenericInfo("Local version: " + localVersion); - Logging.LogGenericInfo("Remote version: " + remoteVersion); + if (releases == null || releases.Count == 0) { + Logging.LogGenericWarning("Could not check latest version!"); + return; + } - int comparisonResult = localVersion.CompareTo(remoteVersion); - if (comparisonResult < 0) { + releaseResponse = releases[0]; + } + + if (string.IsNullOrEmpty(releaseResponse.Tag)) { + Logging.LogGenericWarning("Could not check latest version!"); + return; + } + + Logging.LogGenericInfo("Local version: " + Version); + Logging.LogGenericInfo("Remote version: " + releaseResponse.Tag); + + int comparisonResult = Version.CompareTo(releaseResponse.Tag); + if (comparisonResult >= 0) { + return; + } + + if (!GlobalConfig.AutoUpdates) { Logging.LogGenericInfo("New version is available!"); Logging.LogGenericInfo("Consider updating yourself!"); await Utilities.SleepAsync(5000).ConfigureAwait(false); - } else if (comparisonResult > 0) { - Logging.LogGenericInfo("You're currently using pre-release version!"); - Logging.LogGenericInfo("Be careful!"); + return; + } + + // Auto update logic starts here + if (releaseResponse.Assets == null) { + Logging.LogGenericWarning("Could not proceed with update because that version doesn't include assets!"); + return; + } + + GitHub.Asset binaryAsset = null; + foreach (var asset in releaseResponse.Assets) { + if (string.IsNullOrEmpty(asset.Name) || !asset.Name.Equals(ExecutableName)) { + continue; + } + + binaryAsset = asset; + break; + } + + if (binaryAsset == null) { + Logging.LogGenericWarning("Could not proceed with update because there is no asset that relates to currently running binary!"); + return; + } + + if (string.IsNullOrEmpty(binaryAsset.DownloadURL)) { + Logging.LogGenericWarning("Could not proceed with update because download URL is empty!"); + return; + } + + Logging.LogGenericInfo("Downloading new version..."); + Stream newExe = await WebBrowser.UrlGetToStream(binaryAsset.DownloadURL).ConfigureAwait(false); + if (newExe == null) { + Logging.LogGenericWarning("Could not download new version!"); + return; + } + + // We start deep update logic here + string newExeFile = ExecutableFile + ".new"; + + // Firstly we create new exec + try { + using (FileStream fileStream = File.Open(newExeFile, FileMode.Create)) { + await newExe.CopyToAsync(fileStream).ConfigureAwait(false); + } + } catch (Exception e) { + Logging.LogGenericException(e); + return; + } + + // Now we move current -> old + try { + File.Move(ExecutableFile, oldExeFile); + } catch (Exception e) { + Logging.LogGenericException(e); + try { + // Cleanup + File.Delete(newExeFile); + } catch { } + return; + } + + // Now we move new -> current + try { + File.Move(newExeFile, ExecutableFile); + } catch (Exception e) { + Logging.LogGenericException(e); + try { + // Cleanup + File.Move(oldExeFile, ExecutableFile); + File.Delete(newExeFile); + } catch { } + return; + } + + Logging.LogGenericInfo("Update process is finished! ASF will now restart itself..."); + await Utilities.SleepAsync(5000); + + if (!Restart()) { + // Shit happens + Logging.LogGenericWarning("ASF could not restart itself, you may need to restart it manually!"); + await Utilities.SleepAsync(5000); } } @@ -104,9 +239,19 @@ namespace ArchiSteamFarm { Environment.Exit(exitCode); } - internal static void Restart() { - System.Diagnostics.Process.Start(ExecutableFile, string.Join(" ", Environment.GetCommandLineArgs())); - Exit(); + internal static bool Restart() { + try { + // TODO: This probably won't work on Mono, I wonder if I can make it work at some point + if (Process.Start(ExecutableFile, string.Join(" ", Environment.GetCommandLineArgs())) != null) { + Exit(); + return true; + } else { + return false; + } + } catch (Exception e) { + Logging.LogGenericException(e); + return false; + } } internal static async Task LimitSteamRequestsAsync() { @@ -283,7 +428,7 @@ namespace ArchiSteamFarm { Exit(1); } - Task.Run(async () => await CheckForUpdate().ConfigureAwait(false)).Wait(); + CheckForUpdate().Wait(); // Before attempting to connect, initialize our list of CMs Bot.RefreshCMs(GlobalDatabase.CellID).Wait(); diff --git a/ArchiSteamFarm/Properties/AssemblyInfo.cs b/ArchiSteamFarm/Properties/AssemblyInfo.cs index fc1f591cf..44518182a 100644 --- a/ArchiSteamFarm/Properties/AssemblyInfo.cs +++ b/ArchiSteamFarm/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ArchiSteamFarm")] -[assembly: AssemblyCopyright("Copyright © Łukasz Domeradzki 2015")] +[assembly: AssemblyCopyright("Copyright © Łukasz Domeradzki 2015-2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.0.0.0")] -[assembly: AssemblyFileVersion("2.0.0.0")] +[assembly: AssemblyVersion("2.0.0.1")] +[assembly: AssemblyFileVersion("2.0.0.1")] diff --git a/ArchiSteamFarm/WebBrowser.cs b/ArchiSteamFarm/WebBrowser.cs index 3fcf52841..5e1d60170 100644 --- a/ArchiSteamFarm/WebBrowser.cs +++ b/ArchiSteamFarm/WebBrowser.cs @@ -26,6 +26,7 @@ using HtmlAgilityPack; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; +using System.IO; using System.Net; using System.Net.Http; using System.Text; @@ -81,7 +82,7 @@ namespace ArchiSteamFarm { return await UrlRequest(request, HttpMethod.Post, data, cookies, referer).ConfigureAwait(false); } - internal static async Task UrlGetToContent(string request, Dictionary cookies, string referer = null) { + internal static async Task UrlGetToContent(string request, Dictionary cookies = null, string referer = null) { if (string.IsNullOrEmpty(request)) { return null; } @@ -91,12 +92,28 @@ namespace ArchiSteamFarm { return null; } - HttpContent httpContent = httpResponse.Content; - if (httpContent == null) { + if (httpResponse.Content == null) { return null; } - return await httpContent.ReadAsStringAsync().ConfigureAwait(false); + return await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + } + + internal static async Task UrlGetToStream(string request, Dictionary cookies = null, string referer = null) { + if (string.IsNullOrEmpty(request)) { + return null; + } + + HttpResponseMessage httpResponse = await UrlGet(request, cookies, referer).ConfigureAwait(false); + if (httpResponse == null) { + return null; + } + + if (httpResponse.Content == null) { + return null; + } + + return await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); } internal static async Task UrlGetToHtmlDocument(string request, Dictionary cookies = null, string referer = null) { @@ -145,7 +162,7 @@ namespace ArchiSteamFarm { HttpResponseMessage responseMessage; using (HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, request)) { - if (data != null) { + if (data != null && data.Count > 0) { try { requestMessage.Content = new FormUrlEncodedContent(data); } catch (UriFormatException e) { @@ -162,7 +179,7 @@ namespace ArchiSteamFarm { requestMessage.Headers.Add("Cookie", cookieHeader.ToString()); } - if (referer != null) { + if (!string.IsNullOrEmpty(referer)) { requestMessage.Headers.Referrer = new Uri(referer); }