// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : Statistics.cs // Author(s) : Natalia Portillo // // Component : Core algorithms. // // --[ Description ] ---------------------------------------------------------- // // Handles usage statistics. // // --[ License ] -------------------------------------------------------------- // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // ---------------------------------------------------------------------------- // Copyright © 2011-2024 Natalia Portillo // ****************************************************************************/ using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Text.Json; using System.Threading; using Aaru.CommonTypes.Interop; using Aaru.CommonTypes.Metadata; using Aaru.Console; using Aaru.Database; using Aaru.Database.Models; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Device = Aaru.Devices.Device; using MediaType = Aaru.CommonTypes.MediaType; using OperatingSystem = Aaru.Database.Models.OperatingSystem; using Version = Aaru.Database.Models.Version; namespace Aaru.Core; /// Handles anonymous usage statistics public static class Statistics { const string MODULE_NAME = "Stats"; /// Statistics file semaphore static bool _submitStatsLock; /// Loads saved statistics from disk public static void LoadStats() { try { using var ctx = AaruContext.Create(Settings.Settings.LocalDbPath); if(Settings.Settings.Current.Stats == null) return; ctx.OperatingSystems.Add(new OperatingSystem { Name = DetectOS.GetRealPlatformID().ToString(), Synchronized = false, Version = DetectOS.GetVersion(), Count = 1 }); ctx.Versions.Add(new Version { Name = CommonTypes.Interop.Version.GetVersion(), Synchronized = false, Count = 1 }); ctx.SaveChanges(); } catch(SqliteException ex) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Core.Exception_while_trying_to_save_statistics); AaruConsole.WriteException(ex); } } /// Saves statistics to disk public static void SaveStats() { try { using var ctx = AaruContext.Create(Settings.Settings.LocalDbPath); ctx.SaveChanges(); } catch(SqliteException ex) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Core.Exception_while_trying_to_save_statistics); AaruConsole.WriteException(ex); } if(Settings.Settings.Current.Stats is { ShareStats: true }) SubmitStats(); } /// Submits statistics to Aaru.Server static void SubmitStats() { var submitThread = new Thread(() => { using var ctx = AaruContext.Create(Settings.Settings.LocalDbPath); try { if(_submitStatsLock) return; _submitStatsLock = true; if(ctx.Commands.Any(c => !c.Synchronized) || ctx.Filesystems.Any(c => !c.Synchronized) || ctx.Filters.Any(c => !c.Synchronized) || ctx.MediaFormats.Any(c => !c.Synchronized) || ctx.Partitions.Any(c => !c.Synchronized) || ctx.Medias.Any(c => !c.Synchronized) || ctx.SeenDevices.Any(c => !c.Synchronized) || ctx.OperatingSystems.Any(c => !c.Synchronized) || ctx.Versions.Any(c => !c.Synchronized) || ctx.RemoteApplications.Any(c => !c.Synchronized) || ctx.RemoteArchitectures.Any(c => !c.Synchronized) || ctx.RemoteOperatingSystems.Any(c => !c.Synchronized)) { var dto = new StatsDto(); if(ctx.Commands.Any(c => !c.Synchronized)) { dto.Commands = []; foreach(string nvs in ctx.Commands.Where(c => !c.Synchronized).Select(c => c.Name).Distinct()) { dto.Commands.Add(new NameValueStats { name = nvs, Value = ctx.Commands.LongCount(c => !c.Synchronized && c.Name == nvs) }); } } if(ctx.Filesystems.Any(c => !c.Synchronized)) { dto.Filesystems = []; foreach(string nvs in ctx.Filesystems.Where(c => !c.Synchronized) .Select(c => c.Name) .Distinct()) { dto.Filesystems.Add(new NameValueStats { name = nvs, Value = ctx.Filesystems.LongCount(c => !c.Synchronized && c.Name == nvs) }); } } if(ctx.Filters.Any(c => !c.Synchronized)) { dto.Filters = []; foreach(string nvs in ctx.Filters.Where(c => !c.Synchronized).Select(c => c.Name).Distinct()) { dto.Filters.Add(new NameValueStats { name = nvs, Value = ctx.Filters.LongCount(c => !c.Synchronized && c.Name == nvs) }); } } if(ctx.MediaFormats.Any(c => !c.Synchronized)) { dto.MediaFormats = []; foreach(string nvs in ctx.MediaFormats.Where(c => !c.Synchronized) .Select(c => c.Name) .Distinct()) { dto.MediaFormats.Add(new NameValueStats { name = nvs, Value = ctx.MediaFormats.LongCount(c => !c.Synchronized && c.Name == nvs) }); } } if(ctx.Partitions.Any(c => !c.Synchronized)) { dto.Partitions = []; foreach(string nvs in ctx.Partitions.Where(c => !c.Synchronized).Select(c => c.Name).Distinct()) { dto.Partitions.Add(new NameValueStats { name = nvs, Value = ctx.Partitions.LongCount(c => !c.Synchronized && c.Name == nvs) }); } } if(ctx.Versions.Any(c => !c.Synchronized)) { dto.Versions = []; foreach(string nvs in ctx.Versions.Where(c => !c.Synchronized).Select(c => c.Name).Distinct()) { dto.Versions.Add(new NameValueStats { name = nvs, Value = ctx.Versions.LongCount(c => !c.Synchronized && c.Name == nvs) }); } } if(ctx.Medias.Any(c => !c.Synchronized)) { dto.Medias = []; foreach(string media in ctx.Medias.Where(c => !c.Synchronized).Select(c => c.Type).Distinct()) { if(ctx.Medias.Any(c => !c.Synchronized && c.Type == media && c.Real)) { dto.Medias.Add(new MediaStats { real = true, MediaType = media, Value = ctx.Medias.LongCount(c => !c.Synchronized && c.Type == media && c.Real) }); } if(ctx.Medias.Any(c => !c.Synchronized && c.Type == media && !c.Real)) { dto.Medias.Add(new MediaStats { real = false, MediaType = media, Value = ctx.Medias.LongCount(c => !c.Synchronized && c.Type == media && !c.Real) }); } } } if(ctx.SeenDevices.Any(c => !c.Synchronized)) { dto.Devices = []; foreach(DeviceStat device in ctx.SeenDevices.Where(c => !c.Synchronized)) { dto.Devices.Add(new DeviceStats { Bus = device.Bus, Manufacturer = device.Manufacturer, ManufacturerSpecified = device.Manufacturer is not null, Model = device.Model, Revision = device.Revision }); } } if(ctx.OperatingSystems.Any(c => !c.Synchronized)) { dto.OperatingSystems = []; foreach(string osName in ctx.OperatingSystems.Where(c => !c.Synchronized) .Select(c => c.Name) .Distinct()) { foreach(string osVersion in ctx.OperatingSystems .Where(c => !c.Synchronized && c.Name == osName) .Select(c => c.Version) .Distinct()) { dto.OperatingSystems.Add(new OsStats { name = osName, version = osVersion, Value = ctx.OperatingSystems.LongCount(c => !c.Synchronized && c.Name == osName && c.Version == osVersion) }); } } } if(ctx.RemoteApplications.Any(c => !c.Synchronized)) { dto.RemoteApplications = []; foreach(string remoteAppName in ctx.RemoteApplications.Where(c => !c.Synchronized) .Select(c => c.Name) .Distinct()) { foreach(string remoteAppVersion in ctx.RemoteApplications .Where(c => !c.Synchronized && c.Name == remoteAppName) .Select(c => c.Version) .Distinct()) { dto.RemoteApplications.Add(new OsStats { name = remoteAppName, version = remoteAppVersion, Value = ctx.RemoteApplications.LongCount(c => !c.Synchronized && c.Name == remoteAppName && c.Version == remoteAppVersion) }); } } } if(ctx.RemoteArchitectures.Any(c => !c.Synchronized)) { dto.RemoteArchitectures = []; foreach(string nvs in ctx.RemoteArchitectures.Where(c => !c.Synchronized) .Select(c => c.Name) .Distinct()) { dto.RemoteArchitectures.Add(new NameValueStats { name = nvs, Value = ctx.RemoteArchitectures.LongCount(c => !c.Synchronized && c.Name == nvs) }); } } if(ctx.RemoteOperatingSystems.Any(c => !c.Synchronized)) { dto.RemoteOperatingSystems = []; foreach(string remoteOsName in ctx.RemoteOperatingSystems.Where(c => !c.Synchronized) .Select(c => c.Name) .Distinct()) { foreach(string remoteOsVersion in ctx.RemoteOperatingSystems .Where(c => !c.Synchronized && c.Name == remoteOsName) .Select(c => c.Version) .Distinct()) { dto.RemoteOperatingSystems.Add(new OsStats { name = remoteOsName, version = remoteOsVersion, Value = ctx.RemoteOperatingSystems.LongCount(c => !c.Synchronized && c.Name == remoteOsName && c.Version == remoteOsVersion) }); } } } #if DEBUG System.Console.WriteLine(Localization.Core.Uploading_statistics); #else Aaru.Console.AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Core.Uploading_statistics); #endif string json = JsonSerializer.Serialize(dto, typeof(StatsDto), StatsDtoContext.Default); byte[] jsonBytes = Encoding.UTF8.GetBytes(json); var request = WebRequest.Create("https://www.aaru.app/api/uploadstatsv2"); ((HttpWebRequest)request).UserAgent = $"Aaru {typeof(Version).Assembly.GetName().Version}"; request.Method = "POST"; request.ContentLength = jsonBytes.Length; request.ContentType = "application/json"; Stream reqStream = request.GetRequestStream(); reqStream.Write(jsonBytes, 0, jsonBytes.Length); //jsonStream.CopyTo(reqStream); reqStream.Close(); WebResponse response = request.GetResponse(); if(((HttpWebResponse)response).StatusCode != HttpStatusCode.OK) return; Stream data = response.GetResponseStream(); var reader = new StreamReader(data ?? throw new InvalidOperationException()); string result = reader.ReadToEnd(); data.Close(); response.Close(); if(result != "ok") return; if(ctx.Commands.Any(c => !c.Synchronized)) { foreach(string nvs in ctx.Commands.Where(c => !c.Synchronized).Select(c => c.Name).Distinct()) { Command existing = ctx.Commands.FirstOrDefault(c => c.Synchronized && c.Name == nvs) ?? new Command { Name = nvs, Synchronized = true }; existing.Count += (ulong)ctx.Commands.LongCount(c => !c.Synchronized && c.Name == nvs); ctx.Commands.Update(existing); ctx.Commands.RemoveRange(ctx.Commands.Where(c => !c.Synchronized && c.Name == nvs)); } } if(ctx.Filesystems.Any(c => !c.Synchronized)) { foreach(string nvs in ctx.Filesystems.Where(c => !c.Synchronized) .Select(c => c.Name) .Distinct()) { Filesystem existing = ctx.Filesystems.FirstOrDefault(c => c.Synchronized && c.Name == nvs) ?? new Filesystem { Name = nvs, Synchronized = true }; existing.Count += (ulong)ctx.Filesystems.LongCount(c => !c.Synchronized && c.Name == nvs); ctx.Filesystems.Update(existing); ctx.Filesystems.RemoveRange(ctx.Filesystems.Where(c => !c.Synchronized && c.Name == nvs)); } } if(ctx.Filters.Any(c => !c.Synchronized)) { foreach(string nvs in ctx.Filters.Where(c => !c.Synchronized).Select(c => c.Name).Distinct()) { Filter existing = ctx.Filters.FirstOrDefault(c => c.Synchronized && c.Name == nvs) ?? new Filter { Name = nvs, Synchronized = true }; existing.Count += (ulong)ctx.Filters.LongCount(c => !c.Synchronized && c.Name == nvs); ctx.Filters.Update(existing); ctx.Filters.RemoveRange(ctx.Filters.Where(c => !c.Synchronized && c.Name == nvs)); } } if(ctx.MediaFormats.Any(c => !c.Synchronized)) { foreach(string nvs in ctx.MediaFormats.Where(c => !c.Synchronized) .Select(c => c.Name) .Distinct()) { MediaFormat existing = ctx.MediaFormats.FirstOrDefault(c => c.Synchronized && c.Name == nvs) ?? new MediaFormat { Name = nvs, Synchronized = true }; existing.Count += (ulong)ctx.MediaFormats.LongCount(c => !c.Synchronized && c.Name == nvs); ctx.MediaFormats.Update(existing); ctx.MediaFormats.RemoveRange(ctx.MediaFormats.Where(c => !c.Synchronized && c.Name == nvs)); } } if(ctx.Partitions.Any(c => !c.Synchronized)) { foreach(string nvs in ctx.Partitions.Where(c => !c.Synchronized).Select(c => c.Name).Distinct()) { Partition existing = ctx.Partitions.FirstOrDefault(c => c.Synchronized && c.Name == nvs) ?? new Partition { Name = nvs, Synchronized = true }; existing.Count += (ulong)ctx.Partitions.LongCount(c => !c.Synchronized && c.Name == nvs); ctx.Partitions.Update(existing); ctx.Partitions.RemoveRange(ctx.Partitions.Where(c => !c.Synchronized && c.Name == nvs)); } } if(ctx.Versions.Any(c => !c.Synchronized)) { foreach(string nvs in ctx.Versions.Where(c => !c.Synchronized).Select(c => c.Name).Distinct()) { Version existing = ctx.Versions.FirstOrDefault(c => c.Synchronized && c.Name == nvs) ?? new Version { Name = nvs, Synchronized = true }; existing.Count += (ulong)ctx.Versions.LongCount(c => !c.Synchronized && c.Name == nvs); ctx.Versions.Update(existing); ctx.Versions.RemoveRange(ctx.Versions.Where(c => !c.Synchronized && c.Name == nvs)); } } if(ctx.Medias.Any(c => !c.Synchronized)) { foreach(string media in ctx.Medias.Where(c => !c.Synchronized).Select(c => c.Type).Distinct()) { if(ctx.Medias.Any(c => !c.Synchronized && c.Type == media && c.Real)) { Database.Models.Media existing = ctx.Medias.FirstOrDefault(c => c.Synchronized && c.Type == media && c.Real) ?? new Database.Models.Media { Synchronized = true, Type = media, Real = true }; existing.Count += (ulong)ctx.Medias.LongCount(c => !c.Synchronized && c.Type == media && c.Real); ctx.Medias.Update(existing); ctx.Medias.RemoveRange(ctx.Medias.Where(c => !c.Synchronized && c.Type == media && c.Real)); } if(!ctx.Medias.Any(c => !c.Synchronized && c.Type == media && !c.Real)) continue; { Database.Models.Media existing = ctx.Medias.FirstOrDefault(c => c.Synchronized && c.Type == media && !c.Real) ?? new Database.Models.Media { Synchronized = true, Type = media, Real = false }; existing.Count += (ulong)ctx.Medias.LongCount(c => !c.Synchronized && c.Type == media && !c.Real); ctx.Medias.Update(existing); ctx.Medias.RemoveRange(ctx.Medias.Where(c => !c.Synchronized && c.Type == media && !c.Real)); } } } if(ctx.SeenDevices.Any(c => !c.Synchronized)) { foreach(DeviceStat device in ctx.SeenDevices.Where(c => !c.Synchronized)) { device.Synchronized = true; ctx.Update(device); } } if(ctx.OperatingSystems.Any(c => !c.Synchronized)) { foreach(string osName in ctx.OperatingSystems.Where(c => !c.Synchronized) .Select(c => c.Name) .Distinct()) { foreach(string osVersion in ctx.OperatingSystems .Where(c => !c.Synchronized && c.Name == osName) .Select(c => c.Version) .Distinct()) { OperatingSystem existing = ctx.OperatingSystems.FirstOrDefault(c => c.Synchronized && c.Name == osName && c.Version == osVersion) ?? new OperatingSystem { Synchronized = true, Version = osVersion, Name = osName }; existing.Count += (ulong)ctx.OperatingSystems.LongCount(c => !c.Synchronized && c.Name == osName && c.Version == osVersion); ctx.OperatingSystems.Update(existing); ctx.OperatingSystems.RemoveRange(ctx.OperatingSystems.Where(c => !c.Synchronized && c.Name == osName && c.Version == osVersion)); } } } if(ctx.RemoteApplications.Any(c => !c.Synchronized)) { foreach(string remoteAppName in ctx.RemoteApplications.Where(c => !c.Synchronized) .Select(c => c.Name) .Distinct()) { foreach(string remoteAppVersion in ctx.RemoteApplications .Where(c => !c.Synchronized && c.Name == remoteAppName) .Select(c => c.Version) .Distinct()) { RemoteApplication existing = ctx.RemoteApplications.FirstOrDefault(c => c.Synchronized && c.Name == remoteAppName && c.Version == remoteAppVersion) ?? new RemoteApplication { Synchronized = true, Version = remoteAppVersion, Name = remoteAppName }; existing.Count += (ulong)ctx.RemoteApplications.LongCount(c => !c.Synchronized && c.Name == remoteAppName && c.Version == remoteAppVersion); ctx.RemoteApplications.Update(existing); ctx.RemoteApplications.RemoveRange(ctx.RemoteApplications.Where(c => !c.Synchronized && c.Name == remoteAppName && c.Version == remoteAppVersion)); } } } if(ctx.RemoteArchitectures.Any(c => !c.Synchronized)) { foreach(string nvs in ctx.RemoteArchitectures.Where(c => !c.Synchronized) .Select(c => c.Name) .Distinct()) { RemoteArchitecture existing = ctx.RemoteArchitectures.FirstOrDefault(c => c.Synchronized && c.Name == nvs) ?? new RemoteArchitecture { Name = nvs, Synchronized = true }; existing.Count += (ulong)ctx.RemoteArchitectures.LongCount(c => !c.Synchronized && c.Name == nvs); ctx.RemoteArchitectures.Update(existing); ctx.RemoteArchitectures.RemoveRange(ctx.RemoteArchitectures.Where(c => !c.Synchronized && c.Name == nvs)); } } foreach(string remoteOsName in ctx.RemoteOperatingSystems.Where(c => !c.Synchronized) .Select(c => c.Name) .Distinct()) { foreach(string remoteOsVersion in ctx.RemoteOperatingSystems .Where(c => !c.Synchronized && c.Name == remoteOsName) .Select(c => c.Version) .Distinct()) { RemoteOperatingSystem existing = ctx.RemoteOperatingSystems.FirstOrDefault(c => c.Synchronized && c.Name == remoteOsName && c.Version == remoteOsVersion) ?? new RemoteOperatingSystem { Synchronized = true, Version = remoteOsVersion, Name = remoteOsName }; existing.Count += (ulong)ctx.RemoteOperatingSystems.LongCount(c => !c.Synchronized && c.Name == remoteOsName && c.Version == remoteOsVersion); ctx.RemoteOperatingSystems.Update(existing); ctx.RemoteOperatingSystems.RemoveRange(ctx.RemoteOperatingSystems.Where(c => !c.Synchronized && c.Name == remoteOsName && c.Version == remoteOsVersion)); } } ctx.SaveChanges(); } } catch(WebException) { // Can't connect to the server, do nothing } catch(DbUpdateConcurrencyException) { // Ignore db concurrency errors } // ReSharper disable once RedundantCatchClause catch { #if DEBUG _submitStatsLock = false; if(Debugger.IsAttached) throw; #endif } _submitStatsLock = false; }); submitThread.Start(); } /// Adds the execution of a command to statistics /// Command public static void AddCommand(string command) { if(string.IsNullOrWhiteSpace(command)) return; if(Settings.Settings.Current.Stats is not { DeviceStats: true }) return; using var ctx = AaruContext.Create(Settings.Settings.LocalDbPath); ctx.Commands.Add(new Command { Name = command, Synchronized = false, Count = 1 }); try { ctx.SaveChanges(); } catch(SqliteException ex) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Core.Exception_while_trying_to_save_statistics); AaruConsole.WriteException(ex); } } /// Adds a new filesystem to statistics /// Filesystem name public static void AddFilesystem(string filesystem) { if(string.IsNullOrWhiteSpace(filesystem)) return; if(Settings.Settings.Current.Stats is not { FilesystemStats: true }) return; using var ctx = AaruContext.Create(Settings.Settings.LocalDbPath); ctx.Filesystems.Add(new Filesystem { Name = filesystem, Synchronized = false, Count = 1 }); try { ctx.SaveChanges(); } catch(SqliteException ex) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Core.Exception_while_trying_to_save_statistics); AaruConsole.WriteException(ex); } } /// Adds a new partition scheme to statistics /// Partition scheme name internal static void AddPartition(string partition) { if(string.IsNullOrWhiteSpace(partition)) return; if(Settings.Settings.Current.Stats is not { PartitionStats: true }) return; using var ctx = AaruContext.Create(Settings.Settings.LocalDbPath); ctx.Partitions.Add(new Partition { Name = partition, Synchronized = false, Count = 1 }); try { ctx.SaveChanges(); } catch(SqliteException ex) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Core.Exception_while_trying_to_save_statistics); AaruConsole.WriteException(ex); } } /// Adds a new filter to statistics /// Filter name public static void AddFilter(string filter) { if(string.IsNullOrWhiteSpace(filter)) return; if(Settings.Settings.Current.Stats is not { FilterStats: true }) return; using var ctx = AaruContext.Create(Settings.Settings.LocalDbPath); ctx.Filters.Add(new Filter { Name = filter, Synchronized = false, Count = 1 }); try { ctx.SaveChanges(); } catch(SqliteException ex) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Core.Exception_while_trying_to_save_statistics); AaruConsole.WriteException(ex); } } /// Ads a new media image to statistics /// Media image name public static void AddMediaFormat(string format) { if(string.IsNullOrWhiteSpace(format)) return; if(Settings.Settings.Current.Stats is not { MediaImageStats: true }) return; using var ctx = AaruContext.Create(Settings.Settings.LocalDbPath); ctx.MediaFormats.Add(new MediaFormat { Name = format, Synchronized = false, Count = 1 }); try { ctx.SaveChanges(); } catch(SqliteException ex) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Core.Exception_while_trying_to_save_statistics); AaruConsole.WriteException(ex); } } /// Adds a new device to statistics /// Device public static void AddDevice(Device dev) { if(Settings.Settings.Current.Stats is not { DeviceStats: true }) return; string deviceBus; if(dev.IsUsb) deviceBus = "USB"; else if(dev.IsFireWire) deviceBus = "FireWire"; else deviceBus = dev.Type.ToString(); using var ctx = AaruContext.Create(Settings.Settings.LocalDbPath); if(ctx.SeenDevices.Any(d => d.Manufacturer == dev.Manufacturer && d.Model == dev.Model && d.Revision == dev.FirmwareRevision && d.Bus == deviceBus)) return; ctx.SeenDevices.Add(new DeviceStat { Bus = deviceBus, Manufacturer = dev.Manufacturer, Model = dev.Model, Revision = dev.FirmwareRevision, Synchronized = false }); try { ctx.SaveChanges(); } catch(SqliteException ex) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Core.Exception_while_trying_to_save_statistics); AaruConsole.WriteException(ex); } } /// Adds a new media type to statistics /// Media type /// Set if media was found on a real device, otherwise found on a media image public static void AddMedia(MediaType type, bool real) { if(Settings.Settings.Current.Stats is not { MediaStats: true }) return; using var ctx = AaruContext.Create(Settings.Settings.LocalDbPath); ctx.Medias.Add(new Database.Models.Media { Real = real, Synchronized = false, Type = type.ToString(), Count = 1 }); try { ctx.SaveChanges(); } catch(SqliteException ex) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Core.Exception_while_trying_to_save_statistics); AaruConsole.WriteException(ex); } } /// Adds a new remote to statistics public static void AddRemote(string serverApplication, string serverVersion, string serverOperatingSystem, string serverOperatingSystemVersion, string serverArchitecture) { if(Settings.Settings.Current.Stats is not { MediaStats: true }) return; using var ctx = AaruContext.Create(Settings.Settings.LocalDbPath); ctx.RemoteApplications.Add(new RemoteApplication { Count = 1, Name = serverApplication, Synchronized = false, Version = serverVersion }); ctx.RemoteArchitectures.Add(new RemoteArchitecture { Count = 1, Name = serverArchitecture, Synchronized = false }); ctx.RemoteOperatingSystems.Add(new RemoteOperatingSystem { Count = 1, Name = serverOperatingSystem, Synchronized = false, Version = serverOperatingSystemVersion }); try { ctx.SaveChanges(); } catch(SqliteException ex) { AaruConsole.DebugWriteLine(MODULE_NAME, Localization.Core.Exception_while_trying_to_save_statistics); AaruConsole.WriteException(ex); } } }