using System; using Xamarin.Forms.Xaml; using TINK.Model; using TINK.Model.Connector; using TINK.Model.User.Account; using Xamarin.Forms; using Serilog; using Serilog.Core; using System.Collections.Generic; using Serilog.Events; using TINK.Model.Logging; using TINK.Model.Device; using System.Linq; using MonkeyCache.FileStore; using Plugin.Connectivity; using System.Threading; using TINK.Model.Settings; using TINK.Services.BluetoothLock.Crypto; using TINK.Model.Services.Geolocation; using TINK.Services; using System.Threading.Tasks; using TINK.Services.Permissions; #if ARENDI using Arendi.BleLibrary.Local; #endif // Required for support of binding package, see https://github.com/nuitsjp/Xamarin.Forms.GoogleMaps.Bindings. [assembly: XamlCompilation(XamlCompilationOptions.Compile)] namespace TINK { public partial class App : Application { /// Title of the attachment file. private const string ATTACHMENTTITLE = "Diagnostics.txt"; /// Model root. private static TinkApp m_oModelRoot; /// /// Gets the model root. /// public static TinkApp ModelRoot { get { if (m_oModelRoot != null) { // Root model already exists, nothing to do. return m_oModelRoot; } // Get folder where to read settings from var specialFolders = DependencyService.Get(); var internalPersonalDir = specialFolders.GetInternalPersonalDir(); // Delete attachtment from previous session. DeleteAttachment(internalPersonalDir); // Setup logger using default settings. Log.Logger = new LoggerConfiguration() .MinimumLevel.ControlledBy(new LoggingLevelSwitch { MinimumLevel = Model.Settings.Settings.DEFAULTLOGGINLEVEL }) .WriteTo.Debug() .WriteTo.File(internalPersonalDir, Model.Logging.RollingInterval.Session) .CreateLogger(); // Subscribe to any unhandled/ unobserved exceptions. AppDomain.CurrentDomain.UnhandledException += (sender, unobservedTaskExceptionEventArgs) => { Log.Fatal("Unobserved task exception: {Exception}", unobservedTaskExceptionEventArgs.ExceptionObject); }; TaskScheduler.UnobservedTaskException += (sender, unhandledExceptionEventArgs) => { Log.Fatal("Unhandled exception: {Exception}", unhandledExceptionEventArgs.Exception); }; // Restore last model state from json- file. Dictionary settingsJSON = new Dictionary(); try { settingsJSON = JsonSettingsDictionary.Deserialize(internalPersonalDir); } catch (Exception exception) { Log.Error("Reading application settings from file failed.", exception); } Model.Settings.Settings settings; try { settings = new Model.Settings.Settings( JsonSettingsDictionary.GetGroupFilterMapPage(settingsJSON) ?? GroupFilterHelper.GetMapPageFilterDefaults, // Activate map filtering for meinkonrad JsonSettingsDictionary.GetGoupFilterSettings(settingsJSON) ?? GroupFilterHelper.GetSettingsFilterDefaults,// Activate map filtering for meinkonrad JsonSettingsDictionary.GetCopriHostUri(settingsJSON), JsonSettingsDictionary.GetPollingParameters(settingsJSON), JsonSettingsDictionary.GetMinimumLoggingLevel(settingsJSON), JsonSettingsDictionary.GetIsReportLevelVerbose(settingsJSON), JsonSettingsDictionary.GetExpiresAfter(settingsJSON), JsonSettingsDictionary.GetActiveLockService(settingsJSON), JsonSettingsDictionary.GetConnectTimeout(settingsJSON), JsonSettingsDictionary.GetActiveGeolocationService(settingsJSON), JsonSettingsDictionary.GetCenterMapToCurrentLocation(settingsJSON), JsonSettingsDictionary.GetLogToExternalFolder(settingsJSON), JsonSettingsDictionary.GetIsSiteCachingOn(settingsJSON), JsonSettingsDictionary.GetActiveTheme(settingsJSON) ?? typeof(Themes.Konrad).FullName); } catch (Exception exception) { Log.Error("Deserializing application settings from dictionary failed.", exception); settings = new Model.Settings.Settings(); } if (settings.MinimumLogEventLevel != Model.Settings.Settings.DEFAULTLOGGINLEVEL || settings.LogToExternalFolder) { // Eigher // - logging is not set to default value or // - logging is performed to external folder. // Need to reconfigure. Log.CloseAndFlush(); // Close before modifying logger configuration. Otherwise a sharing vialation occurs. Log.Logger = new LoggerConfiguration() .MinimumLevel.ControlledBy(new LoggingLevelSwitch(settings.MinimumLogEventLevel)) .WriteTo.Debug() .WriteTo.File(!settings.LogToExternalFolder ? internalPersonalDir : specialFolders.GetExternalFilesDir(), Model.Logging.RollingInterval.Session) .CreateLogger(); } // Get auth cookie Log.Debug("Get auth cookie."); IStore store = null; var lastVersion = JsonSettingsDictionary.GetAppVersion(settingsJSON); if (lastVersion > new Version(3, 0, 250)) { // App versions newer than 3.0.173 stored geolocation service in configuration. // Force a switch to typeof(GeolocationService) for versions < 3.0.251 GeolocationServicesContainer.SetActive(settings.ActiveGeolocationService); } store = new Store(); Barrel.ApplicationId = "TINKApp"; var context = SynchronizationContext.Current; var appInfoService = DependencyService.Get(); // Create new app instnace. Log.Debug("Constructing main model..."); m_oModelRoot = new TinkApp( settings, store, // Manages user account (isConnected, activeUri, sessionCookie, mail, expiresAfter) => ConnectorFactory.Create(isConnected, activeUri, $"Meinkonrad/{appInfoService.Version}", sessionCookie, mail, expiresAfter), GeolocationServicesContainer, null, /* locksService */ DependencyService.Get(), specialFolders, new Cipher(), #if ARENDI DependencyService.Get(), #else null, #endif isConnectedFunc: () => CrossConnectivity.Current.IsConnected, postAction: (d, obj) => context.Post(d, obj), currentVersion: appInfoService.Version, lastVersion: JsonSettingsDictionary.GetAppVersion(settingsJSON), whatsNewShownInVersion: JsonSettingsDictionary.GetWhatsNew(settingsJSON) ?? settingsJSON.GetAppVersion()); Log.Debug("Main model successfully constructed."); return m_oModelRoot; } } /// /// Entry point of application. /// public App() { InitializeComponent(); #if USEFLYOUT // Use flyout page. MainPage = ModelRoot.WhatsNew.IsShowRequired ? new View.WhatsNew.WhatsNewPage(() => MainPage = new View.Root.RootPage()) // Show whats new info. : (Page)new View.Root.RootPage(); // Just start sharee- app #else // Use shell. MainPage = ModelRoot.WhatsNew.IsShowRequired ? new View.WhatsNew.WhatsNewPage(() => MainPage = new View.RootShell.AppShell()) // Show whats new info. : (Page)new View.RootShell.AppShell(); // Just start sharee- app #endif } /// Concatenates all log files to a single one. /// Full file name of attachment. public static string CreateAttachment() { var sessionLogFiles = Log.Logger.GetLogFiles().ToArray(); if (sessionLogFiles.Length < 1) { // Either // - there is no logging file // - an error occurred getting list of log files. return string.Empty; } var fullLogFileName = System.IO.Path.Combine(ModelRoot.LogFileParentFolder, ATTACHMENTTITLE); // Stop logging to avoid file access exception. Log.CloseAndFlush(); System.IO.File.WriteAllLines( fullLogFileName, sessionLogFiles.SelectMany(name => (new List { $"{{\"SessionFileName\":\"{name}\"}}" }) .Concat(System.IO.File.ReadLines(name).ToArray()))); // Resume logging Log.Logger = new LoggerConfiguration() .MinimumLevel.ControlledBy(new LoggingLevelSwitch { MinimumLevel = ModelRoot.Level.MinimumLevel }) .WriteTo.Debug() .WriteTo.File(ModelRoot.LogFileParentFolder, Model.Logging.RollingInterval.Session) .CreateLogger(); return fullLogFileName; } /// Deletes an attachment if there is one. /// Folder to delete, is null folder is queried from model. private static void DeleteAttachment(string folder = null) { var attachment = System.IO.Path.Combine(folder ?? ModelRoot.LogFileParentFolder, ATTACHMENTTITLE); if (!System.IO.File.Exists(attachment)) { // No attachment found. return; } System.IO.File.Delete(attachment); } protected override void OnSleep() { // Handle when your app sleeps Log.CloseAndFlush(); } protected override void OnResume() { DeleteAttachment(); Log.Logger = new LoggerConfiguration() .MinimumLevel.ControlledBy(new LoggingLevelSwitch { MinimumLevel = ModelRoot.Level.MinimumLevel }) .WriteTo.Debug() .WriteTo.File(ModelRoot.LogFileParentFolder, Model.Logging.RollingInterval.Session) .CreateLogger(); } /// Gets the current logging level. /// private static LogEventLevel GetCurrentLogEventLevel() { foreach (LogEventLevel level in Enum.GetValues(typeof(LogEventLevel))) { if (Log.IsEnabled(level)) return level; } return LogEventLevel.Error; } /// /// Holds the permission service instance. /// private static ILocationPermission _PermissionsService = null; /// /// Service to manage permissions (location) of the app. /// public static ILocationPermission PermissionsService { get { if (_PermissionsService != null) return _PermissionsService; _PermissionsService = new Services.Permissions.Essentials.Permissions(); return _PermissionsService; } } /// /// Service to manage bluetooth stack. /// public static Plugin.BLE.Abstractions.Contracts.IBluetoothLE BluetoothService => Plugin.BLE.CrossBluetoothLE.Current; /// /// Service container to manage geolocation services. /// public static IServicesContainer GeolocationServicesContainer { get; } = new ServicesContainerMutable( new HashSet { new LastKnownGeolocationService(DependencyService.Get()), new SimulatedGeolocationService(DependencyService.Get()), new GeolocationService(DependencyService.Get()) }, Model.Settings.Settings.DefaultLocationService.FullName); } }