sharee.bike-App/TINK/TINK/App.xaml.cs
2021-08-28 10:04:10 +02:00

288 lines
12 KiB
C#

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 Plugin.Permissions;
using TINK.Services.BluetoothLock.Crypto;
using TINK.Model.Services.Geolocation;
using TINK.Services;
using System.Threading.Tasks;
#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
{
/// <summary>Title of the attachment file.</summary>
private const string ATTACHMENTTITLE = "Diagnostics.txt";
/// <summary> Model root. </summary>
private static TinkApp m_oModelRoot;
/// <summary>
/// Gets the model root.
/// </summary>
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<ISpecialFolder>();
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<string, string> settingsJSON = new Dictionary<string, string>();
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),
JsonSettingsDictionary.GetGoupFilterSettings(settingsJSON),
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));
}
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, 173))
GeolocationServicesContainer.SetActive(settings.ActiveGeolocationService);
store = new Store();
Barrel.ApplicationId = "TINKApp";
var context = SynchronizationContext.Current;
var appInfoService = DependencyService.Get<IAppInfo>();
// 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, $"sharee.bike/{appInfoService.Version}", sessionCookie, mail, expiresAfter),
GeolocationServicesContainer,
null, /* locksService */
DependencyService.Get<ISmartDevice>(),
specialFolders,
new Cipher(),
null, // Permissions, no more used.
#if ARENDI
DependencyService.Get<ICentral>(),
#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;
}
}
/// <summary>
/// Entry point of application.
/// </summary>
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
}
/// <summary> Concatenates all log files to a single one. </summary>
/// <returns>Full file name of attachment.</returns>
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<string> { $"{{\"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;
}
/// <summary>Deletes an attachment if there is one.</summary>
/// <param name="folder">Folder to delete, is null folder is queried from model.</param>
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();
}
/// <summary> Gets the current logging level.</summary>
/// <returns></returns>
private static LogEventLevel GetCurrentLogEventLevel()
{
foreach (LogEventLevel level in Enum.GetValues(typeof(LogEventLevel)))
{
if (Log.IsEnabled(level))
return level;
}
return LogEventLevel.Error;
}
/// <summary>
/// Service to manage permissions (location) of the app.
/// </summary>
public static Plugin.Permissions.Abstractions.IPermissions PermissionsService => CrossPermissions.Current;
/// <summary>
/// Service to manage bluetooth stack.
/// </summary>
public static Plugin.BLE.Abstractions.Contracts.IBluetoothLE BluetoothService => Plugin.BLE.CrossBluetoothLE.Current;
/// <summary>
/// Service container to manage geolocation services.
/// </summary>
public static IServicesContainer<IGeolocation> GeolocationServicesContainer { get; }
= new Services.ServicesContainerMutable<Model.Services.Geolocation.IGeolocation>(
new HashSet<Model.Services.Geolocation.IGeolocation> {
new LastKnownGeolocationService(DependencyService.Get<IGeolodationDependent>()),
new SimulatedGeolocationService(DependencyService.Get<IGeolodationDependent>()),
new GeolocationService(DependencyService.Get<IGeolodationDependent>()) },
typeof(LastKnownGeolocationService).FullName);
}
}