using System; using System.Collections.Generic; using System.Runtime.Serialization; using TINK.Model.Connector; using TINK.Model.Device; using TINK.Settings; using TINK.Model.User.Account; using TINK.Model.Settings; using TINK.Model.Logging; using Serilog.Events; using Serilog.Core; using Serilog; using Plugin.Connectivity; using System.Threading; using TINK.Services.BluetoothLock; using TINK.Model.Services.Geolocation; using TINK.Model.Services.CopriApi.ServerUris; using TINK.Services.BluetoothLock.Crypto; using TINK.ViewModel.Map; using TINK.ViewModel.Settings; using TINK.Services; using TINK.Services.BluetoothLock.BLE; using Xamarin.Forms; using TINK.Model.Station; namespace TINK.Model { [DataContract] public class TinkApp : ITinkApp { /// Delegate used by login view to commit user name and password. /// Mail address used as id login. /// Password for login. /// True if setting credentials succeeded. public delegate bool SetCredentialsDelegate(string p_strMailAddress, string p_strPassword); /// Returns the id of the app (sharee.bike) to be identified by copri. public static string MerchantId => "oiF2kahH"; /// /// Holds status about whants new page. /// public WhatsNew WhatsNew { get; private set; } /// Sets flag whats new page was already shown to true. public void SetWhatsNewWasShown() => WhatsNew = WhatsNew.SetWasShown(); /// Holds uris of copri servers. public CopriServerUriList Uris { get; } /// Holds the filters loaded from settings. public IGroupFilterSettings FilterGroupSetting { get; set; } /// Holds the filter which is applied on the map view. Either TINK or Konrad stations are displayed. private IGroupFilterMapPage m_oFilterDictionaryMapPage; /// Holds the filter which is applied on the map view. Either TINK or Konrad stations are displayed. public IGroupFilterMapPage GroupFilterMapPage { get => m_oFilterDictionaryMapPage; set => m_oFilterDictionaryMapPage = value ?? new GroupFilterMapPage(); } /// Value indicating whether map is centerted to current position or not. public bool CenterMapToCurrentLocation { get; set; } /// Gets the minimum logging level. public LogEventLevel MinimumLogEventLevel { get; set; } /// Gets a value indicating whether reporting level is verbose or not. public bool IsReportLevelVerbose { get; set; } /// Holds the uri which is applied after restart. public Uri NextActiveUri { get; set; } /// Saves object to file. public void Save() => JsonSettingsDictionary.Serialize( SettingsFileFolder, new Dictionary() .SetGroupFilterMapPage(GroupFilterMapPage) .SetCopriHostUri(NextActiveUri.AbsoluteUri) .SetPollingParameters(Polling) .SetGroupFilterSettings(FilterGroupSetting) .SetAppVersion(AppVersion) .SetMinimumLoggingLevel(MinimumLogEventLevel) .SetIsReportLevelVerbose(IsReportLevelVerbose) .SetExpiresAfter(ExpiresAfter) .SetWhatsNew(AppVersion) .SetActiveLockService(LocksServices.Active.GetType().FullName) .SetActiveGeolocationService(GeolocationServices.Active.GetType().FullName) .SetCenterMapToCurrentLocation(CenterMapToCurrentLocation) .SetLogToExternalFolder(LogToExternalFolder) .SetConnectTimeout(LocksServices.Active.TimeOut.MultiConnect) .SetIsSiteCachingOn(IsSiteCachingOn) .SetActiveTheme(Themes.Active.GetType().FullName)); /// /// Update connector from filters when /// - login state changes /// - view is toggled (TINK to Kornrad and vice versa) /// public void UpdateConnector() { // Create filtered connector. m_oConnector = FilteredConnectorFactory.Create( FilterGroupSetting.DoFilter(ActiveUser.DoFilter(GroupFilterMapPage.DoFilter())), m_oConnector.Connector); } /// Polling periode. public PollingParameters Polling { get; set; } public TimeSpan ExpiresAfter { get; set; } /// Holds the version of the app. public Version AppVersion { get; } /// /// Holds the default polling value. /// #if USCSHARP9 public TimeSpan DefaultPolling => new (0, 0, 10); #else public TimeSpan DefaultPolling => new TimeSpan(0, 0, 10); #endif /// Constructs TinkApp object. /// /// /// /// /// Null in productive context. Service to querry geoloation for testing purposes. Parameter can be made optional. /// Null in productive context. Service to control locks/ get locks information for testing proposes. Parameter can be made optional. /// Object allowing platform specific operations. /// /// /// True if connector has access to copri server, false if cached values are used. /// Version of the app. If null version is set to a fixed dummy value (3.0.122) for testing purposes. /// Version of app which was used before this session. /// Holds /// - the version when whats new info was shown last or /// - version of application used last if whats new functionality was not implemented in this version or /// - null if app is installed for the first time. /// /// public TinkApp( Settings.Settings settings, IStore accountStore, Func connectorFactory, IServicesContainer geolocationServicesContainer, ILocksService locksService, ISmartDevice device, ISpecialFolder specialFolder, ICipher cipher, object arendiCentral = null, Func isConnectedFunc = null, Action postAction = null, Version currentVersion = null, Version lastVersion = null, Version whatsNewShownInVersion = null) { PostAction = postAction ?? ((d, obj) => d(obj)); ConnectorFactory = connectorFactory ?? throw new ArgumentException("Can not instantiate TinkApp- object. No connector factory object available."); Cipher = cipher ?? new Cipher(); var locksServices = locksService != null ? new HashSet { locksService } : new HashSet { new LockItByScanServiceEventBased(Cipher), new LockItByScanServicePolling(Cipher), new LockItByGuidService(Cipher), #if BLUETOOTHLE // Requires LockItBluetoothle library. new Bluetoothle.LockItByGuidService(Cipher), #endif #if ARENDI // Requires LockItArendi library. new Arendi.LockItByGuidService(Cipher, arendiCentral), new Arendi.LockItByScanService(Cipher, arendiCentral), #endif new LocksServiceInReach(), new LocksServiceOutOfReach(), }; LocksServices = new LocksServicesContainerMutable( lastVersion >= new Version(3, 0, 173) ? settings.ActiveLockService : LocksServicesContainerMutable.DefaultLocksservice, locksServices); LocksServices.SetTimeOut(settings.ConnectTimeout); Themes = new ServicesContainerMutable( new HashSet { new Themes.Konrad(), new Themes.ShareeBike(), new Themes.LastenradBayern() }, settings.ActiveTheme); GeolocationServices = geolocationServicesContainer ?? throw new ArgumentException($"Can not instantiate {nameof(TinkApp)}- object. No geolocation services container object available."); FilterGroupSetting = settings.GroupFilterSettings; GroupFilterMapPage = settings.GroupFilterMapPage; CenterMapToCurrentLocation = settings.CenterMapToCurrentLocation; SmartDevice = device ?? throw new ArgumentException("Can not instantiate TinkApp- object. No device information provider available."); if (specialFolder == null) { throw new ArgumentException("Can not instantiate TinkApp- object. No special folder provider available."); } // Set logging level. Level.MinimumLevel = settings.MinimumLogEventLevel; LogToExternalFolder = settings.LogToExternalFolder; IsSiteCachingOn = settings.IsSiteCachingOn; ExternalFolder = specialFolder.GetExternalFilesDir(); SettingsFileFolder = specialFolder.GetInternalPersonalDir(); ActiveUser = new User.User( accountStore.GetType().Name == "StoreLegacy" ? new Store() : accountStore, accountStore.Load().Result, device.Identifier); this.isConnectedFunc = isConnectedFunc ?? (() => CrossConnectivity.Current.IsConnected); ExpiresAfter = settings.ExpiresAfter; // Create filtered connector for offline mode. m_oConnector = FilteredConnectorFactory.Create( FilterGroupSetting.DoFilter(GroupFilterMapPage.DoFilter()), ConnectorFactory(GetIsConnected(), settings.ActiveUri, ActiveUser.SessionCookie, ActiveUser.Mail, ExpiresAfter)); // Get uris from file. // Initialize all settings to defaults // Process uris. Uris = new CopriServerUriList(settings.ActiveUri); NextActiveUri = Uris.ActiveUri; Polling = settings.PollingParameters ?? throw new ArgumentException("Can not instantiate TinkApp- object. Polling parameters must never be null."); AppVersion = currentVersion ?? new Version(3, 0, 122); MinimumLogEventLevel = settings.MinimumLogEventLevel; IsReportLevelVerbose = settings.IsReportLevelVerbose; WhatsNew = new WhatsNew(AppVersion, lastVersion, whatsNewShownInVersion); if (Themes.Active.GetType().FullName == typeof(Themes.ShareeBike).FullName) { // Nothing to do. // Theme to activate is default theme. return; } // Set active app theme ICollection mergedDictionaries = Application.Current.Resources.MergedDictionaries; if (mergedDictionaries == null) { Log.ForContext().Error("No merged dictionary available."); return; } mergedDictionaries.Clear(); if (Themes.Active.GetType().FullName == typeof(Themes.Konrad).FullName) { mergedDictionaries.Add(new Themes.Konrad()); } else if (Themes.Active.GetType().FullName == typeof(Themes.LastenradBayern).FullName) { mergedDictionaries.Add(new Themes.LastenradBayern()); } else { Log.ForContext().Debug($"No theme {Themes.Active} found."); } } /// Holds the user of the app. [DataMember] public User.User ActiveUser { get; } /// Reference of object which provides device information. public ISmartDevice SmartDevice { get; } /// Holds delegate to determine whether device is connected or not. private readonly Func isConnectedFunc; /// Gets whether device is connected to internet or not. public bool GetIsConnected() => isConnectedFunc(); /// Holds the folder where settings files are stored. public string SettingsFileFolder { get; } /// Holds folder parent of the folder where log files are stored. public string LogFileParentFolder => LogToExternalFolder && !string.IsNullOrEmpty(ExternalFolder) ? ExternalFolder : SettingsFileFolder; /// Holds a value indicating whether to log to external or internal folder. public bool LogToExternalFolder { get; set; } /// Holds a value indicating whether Site caching is on or off. public bool IsSiteCachingOn { get; set; } /// External folder. public string ExternalFolder { get; } public ICipher Cipher { get; } /// Name of the station which is selected. public IStation SelectedStation { get; set; } = new NullStation(); /// Holds the stations availalbe. public IEnumerable Stations { get; set; } = new List(); /// Action to post to GUI thread. public Action PostAction { get; } /// Function which creates a connector depending on connected status. private Func ConnectorFactory { get; } /// Holds the object which provides offline data. private IFilteredConnector m_oConnector; /// Holds the system to copri. public IFilteredConnector GetConnector(bool isConnected) { if (m_oConnector.IsConnected == isConnected && m_oConnector.Command.SessionCookie == ActiveUser.SessionCookie) { // Neither connection nor logged in stated changed. return m_oConnector; } // Connected state changed. New connection object has to be created. m_oConnector = FilteredConnectorFactory.Create( FilterGroupSetting.DoFilter(ActiveUser.DoFilter(GroupFilterMapPage.DoFilter())), ConnectorFactory( isConnected, Uris.ActiveUri, ActiveUser.SessionCookie, ActiveUser.Mail, ExpiresAfter)); return m_oConnector; } /// Manages the different types of LocksService objects. public LocksServicesContainerMutable LocksServices { get; set; } /// Holds available app themes. public IServicesContainer GeolocationServices { get; } /// Manages the different types of LocksService objects. public ServicesContainerMutable Themes { get; } /// Object to switch logging level. private LoggingLevelSwitch m_oLoggingLevelSwitch; /// /// Object to allow swithing logging level /// public LoggingLevelSwitch Level { get { if (m_oLoggingLevelSwitch == null) { m_oLoggingLevelSwitch = new LoggingLevelSwitch { // Set warning level to error. MinimumLevel = Settings.Settings.DEFAULTLOGGINLEVEL }; } return m_oLoggingLevelSwitch; } } /// Updates logging level. /// New level to set. public void UpdateLoggingLevel(LogEventLevel p_oNewLevel) { if (Level.MinimumLevel == p_oNewLevel) { // Nothing to do. return; } Log.CloseAndFlush(); // Close before modifying logger configuration. Otherwise a sharing vialation occurs. Level.MinimumLevel = p_oNewLevel; // Update logging Log.Logger = new LoggerConfiguration() .MinimumLevel.ControlledBy(Level) .WriteTo.Debug() .WriteTo.File(LogFileParentFolder, Logging.RollingInterval.Session) .CreateLogger(); } } }