Code updated to 3.0.238

This commit is contained in:
Oliver Hauff 2021-06-26 20:57:55 +02:00
parent 3302d80678
commit 9c6a1fa92b
257 changed files with 7763 additions and 2861 deletions

View file

@ -21,13 +21,13 @@ namespace TINK.Model.Bike.BC
/// <summary> Constructs a bike object.</summary>
protected BikeInfo(
IStateInfo stateInfo,
int id,
string id,
bool? isDemo = DEFAULTVALUEISDEMO,
IEnumerable<string> group = null,
WheelType? wheelType = null,
TypeOfBike? typeOfBike = null,
string description = null,
int? currentStationId = null,
string currentStationId = null,
Uri operatorUri = null,
TariffDescription tariffDescription = null)
{
@ -63,8 +63,8 @@ namespace TINK.Model.Bike.BC
/// <param name="tariffDescription">Hold tariff description of bike.</param>
/// <param name="wheelType"></param>
public BikeInfo(
int id,
int? currentStationId,
string id,
string currentStationId,
Uri operatorUri = null,
TariffDescription tariffDescription = null,
bool? isDemo = DEFAULTVALUEISDEMO,
@ -99,13 +99,13 @@ namespace TINK.Model.Bike.BC
/// <param name="code">Booking code.</param>
/// <param name="p_oDateTimeNowProvider">Date time provider to calculate reaining time.</param>
public BikeInfo(
int id,
string id,
bool? isDemo,
IEnumerable<string> group,
WheelType? wheelType,
TypeOfBike? typeOfBike,
string description,
int? stationId,
string stationId,
Uri operatorUri,
TariffDescription tariffDescription,
DateTime requestedAt,
@ -142,13 +142,13 @@ namespace TINK.Model.Bike.BC
/// <param name="mailAddress">Mail address of user which booked bike.</param>
/// <param name="code">Booking code.</param>
public BikeInfo(
int id,
string id,
bool? isDemo,
IEnumerable<string> group,
WheelType? wheelType,
TypeOfBike? typeOfBike,
string description,
int? currentStationId,
string currentStationId,
Uri operatorUri,
TariffDescription tariffDescription,
DateTime bookedAt,
@ -179,7 +179,7 @@ namespace TINK.Model.Bike.BC
/// <summary>
/// Station a which bike is located, null otherwise.
/// </summary>
public int? CurrentStation { get; }
public string CurrentStation { get; }
/// <summary> Holds description about the tarif. </summary>
public TariffDescription TariffDescription { get; }
@ -192,7 +192,7 @@ namespace TINK.Model.Bike.BC
get { return m_oStateInfo; }
}
public int Id => Bike.Id;
public string Id => Bike.Id;
public WheelType? WheelType => Bike.WheelType;
@ -210,7 +210,7 @@ namespace TINK.Model.Bike.BC
/// </summary>
public new string ToString()
{
return $"Id={Bike.Id}{(Bike.WheelType != null ? $", wheel(s)={Bike.WheelType}" : string.Empty)}{(Bike.TypeOfBike != null ? $"type={Bike.TypeOfBike}" : "")}, state={State}, location={(CurrentStation.HasValue ? $"Station {CurrentStation}" : "On the road")}, is demo={IsDemo}.";
return $"Id={Bike.Id}{(Bike.WheelType != null ? $", wheel(s)={Bike.WheelType}" : string.Empty)}{(Bike.TypeOfBike != null ? $"type={Bike.TypeOfBike}" : "")}, state={State}, location={(!string.IsNullOrEmpty(CurrentStation)? $"Station {CurrentStation}" : "On the road")}, is demo={IsDemo}.";
}
}
}

View file

@ -29,13 +29,13 @@ namespace TINK.Model.Bike.BC
/// <param name="tariffDescription">Hold tariff description of bike.</param>
/// <param name="stateInfo">Bike state info.</param>
protected BikeInfoMutable(
int id,
string id,
bool isDemo = BikeInfo.DEFAULTVALUEISDEMO,
IEnumerable<string> group = null,
WheelType? wheelType = null,
TypeOfBike? typeOfBike = null,
string description = null,
int? currentStationId = null,
string currentStationId = null,
Uri operatorUri = null,
TariffDescription tariffDescription = null,
Func<DateTime> dateTimeProvider = null,
@ -71,7 +71,7 @@ namespace TINK.Model.Bike.BC
/// Station a which bike is located, null otherwise.
/// </summary>
[DataMember]
public int? CurrentStation { get; }
public string CurrentStation { get; }
/// <summary> Holds description about the tarif. </summary>
[DataMember]
@ -94,7 +94,7 @@ namespace TINK.Model.Bike.BC
/// <summary> Unused member. </summary>
IStateInfoMutable IBikeInfoMutable.State => m_oStateInfo;
public int Id => m_oBike.Id;
public string Id => m_oBike.Id;
public bool IsDemo { get; }
@ -118,7 +118,7 @@ namespace TINK.Model.Bike.BC
/// <returns></returns>
public new string ToString()
{
return $"Id={Id}{(WheelType != null ? $", wheel(s)={WheelType}" : string.Empty)}{(TypeOfBike != null ? $", type={TypeOfBike}" : "")}, demo={IsDemo}, state={State.ToString()}, location={(CurrentStation.HasValue ? $"Station {CurrentStation}" : "On the road")}.";
return $"Id={Id}{(WheelType != null ? $", wheel(s)={WheelType}" : string.Empty)}{(TypeOfBike != null ? $", type={TypeOfBike}" : "")}, demo={IsDemo}, state={State.ToString()}, location={(!string.IsNullOrEmpty(CurrentStation) ? $"Station {CurrentStation}" : "On the road")}.";
}
}
}

View file

@ -13,7 +13,7 @@ namespace TINK.Model.Bike.BC
/// <summary>
/// Holds the unique id of the bike;
/// </summary>
int Id { get; }
string Id { get; }
/// <summary> True if bike is a demo bike. </summary>
bool IsDemo { get; }
@ -37,7 +37,7 @@ namespace TINK.Model.Bike.BC
/// <summary>
/// Station a which bike is located, null otherwise.
/// </summary>
int? CurrentStation { get; }
string CurrentStation { get; }
/// <summary>
/// Uri of the operator or null, in case of single operator setup.

View file

@ -11,7 +11,7 @@ namespace TINK.Model.Bikes.Bike.BC
/// <summary>
/// Holds the unique id of the bike;
/// </summary>
int Id { get; }
string Id { get; }
/// <summary> True if bike is a demo bike. </summary>
bool IsDemo { get; }
@ -35,7 +35,7 @@ namespace TINK.Model.Bikes.Bike.BC
/// <summary>
/// Station a which bike is located, null otherwise.
/// </summary>
int? CurrentStation { get; }
string CurrentStation { get; }
/// <summary>
/// Holds the rent state of the bike.

View file

@ -29,7 +29,7 @@ namespace TINK.Model.Bike
/// <param name="p_iId">Unique id of bike.</param>
/// <param name="p_strCurrentStationName">Name of station where bike is located, null if bike is on the road.</param>
public Bike(
int p_iId,
string p_iId,
WheelType? wheelType = null,
TypeOfBike? typeOfBike = null,
string description = null)
@ -43,7 +43,7 @@ namespace TINK.Model.Bike
/// <summary>
/// Holds the unique id of the bike;
/// </summary>
public int Id { get; }
public string Id { get; }
/// <summary>
/// Holds the count of wheels.

View file

@ -18,10 +18,10 @@ namespace TINK.Model.Bike.BluetoothLock
/// <param name="tariffDescription">Hold tariff description of bike.</param>
/// <param name="wheelType">Trike, two wheels, mono, ....</param>
public BikeInfo(
int bikeId,
string bikeId,
int lockId,
Guid lockGuid,
int? currentStationId,
string currentStationId,
Uri operatorUri = null,
TariffDescription tariffDescription = null,
bool? isDemo = DEFAULTVALUEISDEMO,
@ -58,7 +58,7 @@ namespace TINK.Model.Bike.BluetoothLock
/// <param name="p_oDateTimeNowProvider">Date time provider to calculate reaining time.</param>
/// <param name="wheelType"></param>
public BikeInfo(
int id,
string id,
int lockId,
Guid lockGuid,
byte[] userKey,
@ -66,7 +66,7 @@ namespace TINK.Model.Bike.BluetoothLock
byte[] seed,
DateTime requestedAt,
string mailAddress,
int? currentStationId,
string currentStationId,
Uri operatorUri,
TariffDescription tariffDescription,
Func<DateTime> dateTimeProvider,
@ -106,7 +106,7 @@ namespace TINK.Model.Bike.BluetoothLock
/// <param name="tariffDescription">Hold tariff description of bike.</param>
/// <param name="wheelType"></param>
public BikeInfo(
int id,
string id,
int lockId,
Guid lockGuid,
byte[] userKey,
@ -114,7 +114,7 @@ namespace TINK.Model.Bike.BluetoothLock
byte[] seed,
DateTime bookedAt,
string mailAddress,
int? currentStationId,
string currentStationId,
Uri operatorUri,
TariffDescription tariffDescription = null,
bool? isDemo = DEFAULTVALUEISDEMO,

View file

@ -5,8 +5,9 @@ namespace TINK.Model.Bikes.Bike
/// <summary>
/// Holds tariff info for a single bike.
/// </summary>
#if USCSHARP9
public record TariffDescription
{
{
/// <summary>
/// Name of the tariff.
/// </summary>
@ -37,4 +38,38 @@ namespace TINK.Model.Bikes.Bike
/// </summary>
public double MaxFeeEuroPerDay { get; init; }
}
#else
public class TariffDescription
{
/// <summary>
/// Name of the tariff.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Number of the tariff.
/// </summary>
public int? Number { get; set; }
/// <summary>
/// Costs per hour in euro.
/// </summary>
public double FeeEuroPerHour { get; set; }
/// <summary>
/// Costs of the abo per month.
/// </summary>
public double AboEuroPerMonth { get; set; }
/// <summary>
/// Costs per hour in euro.
/// </summary>
public TimeSpan FreeTimePerSession { get; set; }
/// <summary>
/// Max. costs per day in euro.
/// </summary>
public double MaxFeeEuroPerDay { get; set; }
}
#endif
}

View file

@ -11,37 +11,37 @@ namespace TINK.Model.Bike
public class BikeCollection : IBikeDictionary<BikeInfo>
{
/// <summary> Holds the bike dictionary object.</summary>
private Dictionary<int, BikeInfo> BikeDictionary { get; }
private Dictionary<string, BikeInfo> BikeDictionary { get; }
/// <summary>Constructs an empty bike info dictionary object.</summary>
public BikeCollection()
{
BikeDictionary = new Dictionary<int, BikeInfo>();
BikeDictionary = new Dictionary<string, BikeInfo>();
}
/// <summary> Constructs a bike collection object.</summary>
/// <param name="bikeDictionary"></param>
public BikeCollection(Dictionary<int, BikeInfo> bikeDictionary)
public BikeCollection(Dictionary<string, BikeInfo> bikeDictionary)
{
BikeDictionary = bikeDictionary ??
throw new ArgumentNullException(nameof(bikeDictionary), "Can not construct BikeCollection object.");
}
/// <summary> Gets a bike by its id.</summary>
/// <param name="p_iId">Id of the bike to get.</param>
/// <param name="id">Id of the bike to get.</param>
/// <returns></returns>
public BikeInfo GetById(int p_iId)
public BikeInfo GetById(string id)
{
return BikeDictionary.FirstOrDefault(x => x.Key == p_iId).Value;
return BikeDictionary.FirstOrDefault(x => x.Key == id).Value;
}
/// <summary> Gets the count of bikes. </summary>
public int Count => BikeDictionary.Count;
/// <summary> Gets if a bike with given id exists.</summary>
/// <param name="p_iId">Id of bike.</param>
/// <param name="id">Id of bike.</param>
/// <returns>True if bike is contained, false otherwise.</returns>
public bool ContainsKey(int p_iId) => BikeDictionary.Keys.Contains(p_iId);
public bool ContainsKey(string id) => BikeDictionary.Keys.Contains(id);
/// <summary> Gets the enumerator. </summary>
/// <returns>Enumerator object.</returns>

View file

@ -14,11 +14,11 @@ namespace TINK.Model
/// <returns>BikeCollection holding bikes at given station or empty BikeCollection, if there are no bikes.</returns>
public static BikeCollection GetAtStation(
this BikeCollection bikesAtAnyStation,
int? selectedStation)
string selectedStation)
{
return new BikeCollection(bikesAtAnyStation?
.Where(bike => selectedStation.HasValue && bike.CurrentStation == selectedStation.Value)
.ToDictionary(x => x.Id) ?? new Dictionary<int, BikeInfo>());
.Where(bike => !string.IsNullOrEmpty(selectedStation) && bike.CurrentStation == selectedStation)
.ToDictionary(x => x.Id) ?? new Dictionary<string, BikeInfo>());
}
/// <summary> Filters bikes by bike type. </summary>
@ -28,7 +28,7 @@ namespace TINK.Model
{
return new BikeCollection(bcAndLockItBikes?
.Where(bike => bike is Bike.BluetoothLock.BikeInfo)
.ToDictionary(x => x.Id) ?? new Dictionary<int, BikeInfo>());
.ToDictionary(x => x.Id) ?? new Dictionary<string, BikeInfo>());
}
}
}

View file

@ -44,7 +44,7 @@ namespace TINK.Model.Bike
// Update bike.
GetById(bikeInfo.Id).State.Load(bikeInfo.State);
if (bikesToBeRemoved.Contains<int>(bikeInfo.Id))
if (bikesToBeRemoved.Contains<string>(bikeInfo.Id))
{
// Remove list from obsolete list.
bikesToBeRemoved.Remove(bikeInfo.Id);
@ -86,20 +86,20 @@ namespace TINK.Model.Bike
private set;
}
public void SetSelectedBike(int p_intId)
public void SetSelectedBike(string id)
{
SelectedBike = GetById(p_intId);
SelectedBike = GetById(id);
}
/// <summary>
/// Gets a bike by its id.
/// </summary>
/// <param name="p_iId"></param>
/// <param name="id"></param>
/// <returns></returns>
public BikeInfoMutable GetById(int p_iId)
public BikeInfoMutable GetById(string id)
{
{
return this.FirstOrDefault(bike => bike.Id == p_iId);
return this.FirstOrDefault(bike => bike.Id == id);
}
}
@ -108,18 +108,18 @@ namespace TINK.Model.Bike
/// </summary>
/// <param name="p_strKey">Key to check.</param>
/// <returns>True if bike exists.</returns>
public bool ContainsKey(int p_iId)
public bool ContainsKey(string id)
{
return GetById(p_iId) != null;
return GetById(id) != null;
}
/// <summary>
/// Removes a bike by its id.
/// </summary>
/// <param name="p_iId">Id of bike to be removed.</param>
public void RemoveById(int p_iId)
/// <param name="id">Id of bike to be removed.</param>
public void RemoveById(string id)
{
var l_oBike = GetById(p_iId);
var l_oBike = GetById(id);
if (l_oBike == null)
{
// Nothing to do if bike does not exists.

View file

@ -14,7 +14,7 @@ namespace TINK.Model.Bike
IEnumerable<BluetoothLock.LockInfo> locksInfo)
{
var updatedBikesCollection = new Dictionary<int, BC.BikeInfo>();
var updatedBikesCollection = new Dictionary<string, BC.BikeInfo>();
foreach (var bikeInfo in bikes)
{

View file

@ -7,29 +7,29 @@ namespace TINK.Model.Bike
/// <summary>
/// Gets a bike by its id.
/// </summary>
/// <param name="p_iId"></param>
/// <param name="id"></param>
/// <returns></returns>
T GetById(int p_iId);
T GetById(string id);
/// <summary>
/// Deteermines whether a bike by given key exists.
/// </summary>
/// <param name="p_strKey">Key to check.</param>
/// <returns>True if bike exists.</returns>
bool ContainsKey(int p_iId);
bool ContainsKey(string id);
}
public interface IBikeDictionaryMutable<T> : IBikeDictionary<T>
{
/// <summary>
/// Removes a bike by its id.
/// </summary>
/// <param name="p_iId">Id of bike to be removed.</param>
void RemoveById(int p_iId);
/// <param name="id">Id of bike to be removed.</param>
void RemoveById(string id);
/// <summary>
/// Adds a new element to dictinary.
/// </summary>
/// <param name="p_oNewElement">New element to add.</param>
void Add(T p_oNewElement);
/// <param name="newElement">New element to add.</param>
void Add(T newElement);
}
}

View file

@ -1,10 +1,11 @@
using Serilog;
using System;
using System.Threading.Tasks;
using TINK.Model.Repository;
using TINK.Model.Repository.Request;
using TINK.Model.Repository.Response;
using TINK.Repository;
using TINK.Repository.Request;
using TINK.Repository.Response;
using TINK.Model.User.Account;
using TINK.Model.Device;
namespace TINK.Model.Connector
{
@ -122,7 +123,8 @@ namespace TINK.Model.Connector
public async Task DoReturn(
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike,
LocationDto location)
LocationDto location,
ISmartDevice smartDevice)
{
Log.ForContext<Command>().Error("Unexpected returning request detected. No user logged in.");
await Task.CompletedTask;
@ -132,7 +134,11 @@ namespace TINK.Model.Connector
/// Submits feedback to copri server.
/// </summary>
/// <param name="userFeedback">Feedback to submit.</param>
#if USCSHARP9
public async Task DoSubmitFeedback(ICommand.IUserFeedback userFeedback, Uri opertorUri)
#else
public async Task DoSubmitFeedback(IUserFeedback userFeedback, Uri opertorUri)
#endif
{
Log.ForContext<Command>().Error("Unexpected submit feedback request detected. No user logged in.");
await Task.CompletedTask;

View file

@ -1,11 +1,12 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.Repository;
using TINK.Model.Repository.Exception;
using TINK.Model.Repository.Request;
using TINK.Model.Repository.Response;
using TINK.Repository;
using TINK.Repository.Exception;
using TINK.Repository.Request;
using TINK.Repository.Response;
using TINK.Model.User.Account;
using TINK.Model.Device;
namespace TINK.Model.Connector
{
@ -30,7 +31,7 @@ namespace TINK.Model.Connector
/// Logs user in.
/// If log in succeeds either and session might be updated if it was no more valid (logged in by an different device).
/// If log in fails (password modified) session cookie is set to empty.
/// If communication fails an TINK.Model.Repository.Exception is thrown.
/// If communication fails an TINK.Repository.Exception is thrown.
/// </summary>
/// <param name="p_oAccount">Account to use for login.</param>
public Task<IAccount> DoLogin(string p_strMail, string p_strPassword, string p_strDeviceId)
@ -242,12 +243,13 @@ namespace TINK.Model.Connector
}
/// <summary> Request to return a bike.</summary>
/// <param name="latitude">Latitude of the bike.</param>
/// <param name="longitude">Longitude of the bike.</param>
/// <param name="bike">Bike to return.</param>
/// <param name="locaton">Position of the bike.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
public async Task DoReturn(
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike,
LocationDto location)
LocationDto location,
ISmartDevice smartDevice)
{
if (bike == null)
{
@ -257,7 +259,7 @@ namespace TINK.Model.Connector
ReservationCancelReturnResponse l_oResponse;
try
{
l_oResponse = (await CopriServer.DoReturn(bike.Id, location, bike.OperatorUri)).GetIsReturnBikeResponseOk(bike.Id);
l_oResponse = (await CopriServer.DoReturn(bike.Id, location, smartDevice, bike.OperatorUri)).GetIsReturnBikeResponseOk(bike.Id);
}
catch (Exception)
{
@ -272,8 +274,12 @@ namespace TINK.Model.Connector
/// Submits feedback to copri server.
/// </summary>
/// <param name="userFeedback">Feedback to submit.</param>
#if USCSHARP9
public async Task DoSubmitFeedback(ICommand.IUserFeedback userFeedback, Uri opertorUri)
=> await CopriServer.DoSubmitFeedback(userFeedback.Message, userFeedback.IsBikeBroken, opertorUri);
=> await CopriServer.DoSubmitFeedback(userFeedback.BikeId, userFeedback.Message, userFeedback.IsBikeBroken, opertorUri);
#else
public async Task DoSubmitFeedback(IUserFeedback userFeedback, Uri opertorUri)
=> await CopriServer.DoSubmitFeedback(userFeedback.BikeId, userFeedback.Message, userFeedback.IsBikeBroken, opertorUri);
#endif
}
}

View file

@ -1,7 +1,8 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Repository.Request;
using TINK.Repository.Request;
using TINK.Model.User.Account;
using TINK.Model.Device;
namespace TINK.Model.Connector
{
@ -44,9 +45,10 @@ namespace TINK.Model.Connector
Task DoBook(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike);
/// <summary> Request to return a bike.</summary>
/// <param name="location">Geolocation of lock when returning bike.</param>
/// <param name="bike">Bike to return.</param>
Task DoReturn(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike, LocationDto geolocation = null);
/// <param name="location">Geolocation of lock when returning bike.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
Task DoReturn(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike, LocationDto geolocation = null, ISmartDevice smartDevice = null);
/// <summary> True if connector has access to copri server, false if cached values are used. </summary>
bool IsConnected { get; }
@ -55,12 +57,15 @@ namespace TINK.Model.Connector
string SessionCookie { get; }
Task DoSubmitFeedback(IUserFeedback userFeedback, Uri opertorUri);
#if USCSHARP9
/// <summary>
/// Feedback given by user when returning bike.
/// </summary>
public interface IUserFeedback
{
/// <summary> Id of the bike to which the feedback is related to.</summary>
string BikeId { get; }
/// <summary>
/// Holds whether bike is broken or not.
/// </summary>
@ -74,12 +79,37 @@ namespace TINK.Model.Connector
/// </summary>
string Message { get; }
}
#endif
}
/// <summary>Defines delegate to be raised whenever login state changes.</summary>
/// <param name="p_oEventArgs">Holds session cookie and mail address if user logged in successfully.</param>
public delegate void LoginStateChangedEventHandler(object p_oSender, LoginStateChangedEventArgs p_oEventArgs);
#if !USCSHARP9
/// <summary>
/// Feedback given by user when returning bike.
/// </summary>
public interface IUserFeedback
{
/// <summary> Id of the bike to which the feedback is related to.</summary>
string BikeId { get; }
/// <summary>
/// Holds whether bike is broken or not.
/// </summary>
bool IsBikeBroken { get; }
/// <summary>
/// Holds either
/// - general feedback
/// - error description of broken bike
/// or both.
/// </summary>
string Message { get; }
}
#endif
/// <summary> Event arguments to notify about changes of logged in state.</summary>
public class LoginStateChangedEventArgs : EventArgs
{

View file

@ -1,9 +1,25 @@

namespace TINK.Model.Connector
{
#if USCSHARP9
public record UserFeedbackDto : ICommand.IUserFeedback
{
public string BikeId { get; init; }
public bool IsBikeBroken { get; init; }
public string Message { get; init; }
}
#else
#if USCSHARP9
public class UserFeedbackDto : ICommand.IUserFeedback
#else
public class UserFeedbackDto : IUserFeedback
#endif
{
public string BikeId { get; set; }
public bool IsBikeBroken { get; set; }
public string Message { get; set; }
}
#endif
}

View file

@ -1,6 +1,6 @@
using System;
using TINK.Model.Services.CopriApi;
using TINK.Model.Repository;
using TINK.Repository;
namespace TINK.Model.Connector
{

View file

@ -1,6 +1,6 @@
using System;
using TINK.Model.Services.CopriApi;
using TINK.Model.Repository;
using TINK.Repository;
namespace TINK.Model.Connector
{

View file

@ -1,13 +1,26 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace TINK.Model.Connector.Filter
{
public static class GroupFilterFactory
{
/// <summary>
/// Creates filter object.
/// </summary>
/// <param name="group">if value consists
/// - list of strings entries are used to filter (intersect) with or if value is
/// - null or an empty list null filter is applied, i.e. filtering is off.</param>
/// <returns>Filtering object.</returns>
/// <remarks>
/// Tread group values of null and empty lists as marker to turn filtering off to handle COPRI responses maximal flexible.
/// </remarks>
public static IGroupFilter Create(IEnumerable<string> group)
{
return group != null ? (IGroupFilter) new IntersectGroupFilter(group) : new NullGroupFilter();
return group != null && group.Count() > 0
? (IGroupFilter) new IntersectGroupFilter(group) :
new NullGroupFilter();
}
}
}

View file

@ -86,29 +86,33 @@ namespace TINK.Model.Connector
/// <returns></returns>
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
{
var result = await m_oInnerQuery.GetBikesAndStationsAsync();
// Bikes and stations from COPRI or cache
var providerBikesAndStations = await m_oInnerQuery.GetBikesAndStationsAsync();
return new Result<StationsAndBikesContainer>(
result.Source,
new StationsAndBikesContainer(
new StationDictionary(result.Response.StationsAll.CopriVersion, DoFilter(result.Response.StationsAll, Filter)),
new BikeCollection(DoFilter(result.Response.Bikes, Filter))),
result.Exception);
// Do filtering.
var filteredStationsDictionary = new StationDictionary(providerBikesAndStations.Response.StationsAll.CopriVersion, DoFilter(providerBikesAndStations.Response.StationsAll, Filter));
var filteredBikesDictionary = new BikeCollection(DoFilter(providerBikesAndStations.Response.Bikes, Filter));
var filteredBikesAndStations = new Result<StationsAndBikesContainer>(
providerBikesAndStations.Source,
new StationsAndBikesContainer(filteredStationsDictionary, filteredBikesDictionary),
providerBikesAndStations.Exception); ;
return filteredBikesAndStations;
}
/// <summary> Filter bikes by group. </summary>
/// <param name="p_oBikes">Bikes to filter.</param>
/// <param name="bikes">Bikes to filter.</param>
/// <returns>Filtered bikes.</returns>
private static Dictionary<int, BikeInfo> DoFilter(BikeCollection p_oBikes, IGroupFilter filter)
private static Dictionary<string, BikeInfo> DoFilter(BikeCollection bikes, IGroupFilter filter)
{
return p_oBikes.Where(x => filter.DoFilter(x.Group).Count() > 0).ToDictionary(x => x.Id);
return bikes.Where(x => filter.DoFilter(x.Group).Count() > 0).ToDictionary(x => x.Id);
}
/// <summary> Filter stations by broup. </summary>
/// <returns></returns>
private static Dictionary<int, Station.Station> DoFilter(StationDictionary p_oStations, IGroupFilter filter)
private static Dictionary<string, IStation> DoFilter(StationDictionary stations, IGroupFilter filter)
{
return p_oStations.Where(x => filter.DoFilter(x.Group).Count() > 0).ToDictionary((x => x.Id));
return stations.Where(x => filter.DoFilter(x.Group).Count() > 0).ToDictionary(x => x.Id);
}
}
}

View file

@ -90,18 +90,18 @@ namespace TINK.Model.Connector
}
/// <summary> Filter bikes by group. </summary>
/// <param name="p_oBikes">Bikes to filter.</param>
/// <param name="bikes">Bikes to filter.</param>
/// <returns>Filtered bikes.</returns>
public static Dictionary<int, BikeInfo> DoFilter(BikeCollection p_oBikes, IEnumerable<string> p_oFilter)
public static Dictionary<string, BikeInfo> DoFilter(BikeCollection bikes, IEnumerable<string> filter)
{
return p_oBikes.Where(x => x.Group.Intersect(p_oFilter).Count() > 0).ToDictionary(x => x.Id);
return bikes.Where(x => x.Group.Intersect(filter).Count() > 0).ToDictionary(x => x.Id);
}
/// <summary> Filter stations by broup. </summary>
/// <returns></returns>
public static Dictionary<int, Station.Station> DoFilter(StationDictionary p_oStations, IEnumerable<string> p_oFilter)
public static Dictionary<string, IStation> DoFilter(StationDictionary stations, IEnumerable<string> p_oFilter)
{
return p_oStations.Where(x => x.Group.Intersect(p_oFilter).Count() > 0).ToDictionary((x => x.Id));
return stations.Where(x => x.Group.Intersect(p_oFilter).Count() > 0).ToDictionary(x => x.Id);
}
}
}

View file

@ -1,5 +1,5 @@
using System;
using TINK.Model.Repository;
using TINK.Repository;
namespace TINK.Model.Connector
{

View file

@ -1,5 +1,5 @@
using System;
using TINK.Model.Repository;
using TINK.Repository;
namespace TINK.Model.Connector
{

View file

@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using TINK.Model.Bike;
using TINK.Model.Services.CopriApi;
using TINK.Model.Repository;
using TINK.Repository;
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
namespace TINK.Model.Connector
@ -70,7 +70,7 @@ namespace TINK.Model.Connector
Log.ForContext<CachedQuery>().Error("Unexpected call to get be bikes occpied detected. No user is logged in.");
return new Result<BikeCollection>(
typeof(CopriCallsMonkeyStore),
await Task.Run(() => new BikeCollection(new Dictionary<int, BikeInfo>())),
await Task.Run(() => new BikeCollection(new Dictionary<string, BikeInfo>())),
new System.Exception("Abfrage der reservierten/ gebuchten Räder nicht möglich. Kein Benutzer angemeldet."));
}

View file

@ -1,10 +1,9 @@
using MonkeyCache.FileStore;
using System;
using System;
using System.Linq;
using System.Threading.Tasks;
using TINK.Model.Bike;
using TINK.Model.Services.CopriApi;
using TINK.Model.Repository;
using TINK.Repository;
namespace TINK.Model.Connector
{
@ -31,73 +30,74 @@ namespace TINK.Model.Connector
/// <summary> Gets all stations including postions.</summary>
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
{
var resultStations = await server.GetStations();
var stationsResponse = await server.GetStations();
if (resultStations.Source == typeof(CopriCallsMonkeyStore)
|| resultStations.Exception != null)
if (stationsResponse.Source == typeof(CopriCallsMonkeyStore)
|| stationsResponse.Exception != null)
{
// Stations were read from cache ==> get bikes availbalbe and occupied from cache as well to avoid inconsistencies
return new Result<StationsAndBikesContainer>(
resultStations.Source,
stationsResponse.Source,
new StationsAndBikesContainer(
resultStations.Response.GetStationsAllMutable(),
stationsResponse.Response.GetStationsAllMutable(),
UpdaterJSON.GetBikesAll(
(await server.GetBikesAvailable(true)).Response,
(await server.GetBikesOccupied(true)).Response,
Mail,
DateTimeProvider)),
resultStations.Exception);
stationsResponse.Exception);
}
var l_oBikesAvailableResponse = await server.GetBikesAvailable();
if (l_oBikesAvailableResponse.Source == typeof(CopriCallsMonkeyStore)
|| l_oBikesAvailableResponse.Exception != null)
var bikesAvailableResponse = await server.GetBikesAvailable();
if (bikesAvailableResponse.Source == typeof(CopriCallsMonkeyStore)
|| bikesAvailableResponse.Exception != null)
{
// Bikes avilable were read from cache ==> get bikes occupied from cache as well to avoid inconsistencies
return new Result<StationsAndBikesContainer>(
l_oBikesAvailableResponse.Source,
bikesAvailableResponse.Source,
new StationsAndBikesContainer(
(await server.GetStations(true)).Response.GetStationsAllMutable(),
UpdaterJSON.GetBikesAll(l_oBikesAvailableResponse.Response,
UpdaterJSON.GetBikesAll(bikesAvailableResponse.Response,
(await server.GetBikesOccupied(true)).Response,
Mail,
DateTimeProvider)),
l_oBikesAvailableResponse.Exception);
bikesAvailableResponse.Exception);
}
var l_oBikesOccupiedResponse = await server.GetBikesOccupied();
if (l_oBikesOccupiedResponse.Source == typeof(CopriCallsMonkeyStore)
|| l_oBikesOccupiedResponse.Exception != null)
var bikesOccupiedResponse = await server.GetBikesOccupied();
if (bikesOccupiedResponse.Source == typeof(CopriCallsMonkeyStore)
|| bikesOccupiedResponse.Exception != null)
{
// Bikes occupied were read from cache ==> get bikes available from cache as well to avoid inconsistencies
return new Result<StationsAndBikesContainer>(
l_oBikesOccupiedResponse.Source,
bikesOccupiedResponse.Source,
new StationsAndBikesContainer(
(await server.GetStations(true)).Response.GetStationsAllMutable(),
UpdaterJSON.GetBikesAll(
(await server.GetBikesAvailable(true)).Response,
l_oBikesOccupiedResponse.Response,
bikesOccupiedResponse.Response,
Mail,
DateTimeProvider)),
l_oBikesOccupiedResponse.Exception);
bikesOccupiedResponse.Exception);
}
// Both types bikes could read from copri => update cache
server.AddToCache(resultStations);
server.AddToCache(l_oBikesAvailableResponse);
server.AddToCache(l_oBikesOccupiedResponse);
server.AddToCache(stationsResponse);
server.AddToCache(bikesAvailableResponse);
server.AddToCache(bikesOccupiedResponse);
var exceptions = new[] { resultStations?.Exception, l_oBikesAvailableResponse?.Exception, l_oBikesOccupiedResponse?.Exception }.Where(x => x != null).ToArray();
var exceptions = new[] { stationsResponse?.Exception, bikesAvailableResponse?.Exception, bikesOccupiedResponse?.Exception }.Where(x => x != null).ToArray();
var stationsMutable = stationsResponse.Response.GetStationsAllMutable();
var bikesMutable = UpdaterJSON.GetBikesAll(
bikesAvailableResponse.Response,
bikesOccupiedResponse.Response,
Mail,
DateTimeProvider);
return new Result<StationsAndBikesContainer>(
resultStations.Source,
new StationsAndBikesContainer(
resultStations.Response.GetStationsAllMutable(),
UpdaterJSON.GetBikesAll(
l_oBikesAvailableResponse.Response,
l_oBikesOccupiedResponse.Response,
Mail,
DateTimeProvider)),
stationsResponse.Source,
new StationsAndBikesContainer(stationsMutable, bikesMutable),
exceptions.Length > 0 ? new AggregateException(exceptions) : null);
}

View file

@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using TINK.Model.Bike;
using TINK.Model.Services.CopriApi;
using TINK.Model.Repository;
using TINK.Repository;
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
namespace TINK.Model.Connector
@ -44,7 +44,7 @@ namespace TINK.Model.Connector
Log.ForContext<Query>().Error("Unexpected call to get be bikes occpied detected. No user is logged in.");
return new Result<BikeCollection>(
typeof(CopriCallsMonkeyStore),
await Task.Run(() => new BikeCollection(new Dictionary<int, BikeInfo>())),
await Task.Run(() => new BikeCollection(new Dictionary<string, BikeInfo>())),
new System.Exception("Abfrage der reservierten/ gebuchten Räder fehlgeschlagen. Kein Benutzer angemeldet."));
}

View file

@ -2,7 +2,7 @@
using System.Threading.Tasks;
using TINK.Model.Bike;
using TINK.Model.Services.CopriApi;
using TINK.Model.Repository;
using TINK.Repository;
namespace TINK.Model.Connector
{

View file

@ -5,8 +5,8 @@ using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using TINK.Model.Bike;
using TINK.Model.Repository.Exception;
using TINK.Model.Repository.Response;
using TINK.Repository.Exception;
using TINK.Repository.Response;
using TINK.Model.Services.CopriApi.ServerUris;
using TINK.Model.State;
@ -51,14 +51,16 @@ namespace TINK.Model.Connector
/// <summary> Gets the position from StationInfo object. </summary>
/// <param name="p_oAuthorizationResponse">Object to get information from.</param>
/// <returns>Position information.</returns>
public static IEnumerable<string> GetGroup(this string p_oGroup)
public static IEnumerable<string> GetGroup(this string[] group)
{
if (string.IsNullOrEmpty(p_oGroup))
if (group == null || group.Length == 0)
{
throw new ArgumentException("Can not get goup form string. Group text can not be null.");
// If not logged in stations groups are empty form COPRI version v4.1.
Log.Debug("Can not get goup form string. Group text can not be null.");
return new List<string>();
}
return new HashSet<string>(p_oGroup.Split(',')).ToList();
return new HashSet<string>(group).ToList();
}
/// <summary> Gets the position from StationInfo object. </summary>
@ -78,9 +80,9 @@ namespace TINK.Model.Connector
{
return p_oStationInfo.station_group.GetGroup();
}
catch (System.Exception l_oException)
catch (Exception l_oException)
{
throw new System.Exception($"Can not get group of stations from text \"{p_oStationInfo.station_group}\".", l_oException);
throw new Exception($"Can not get group of stations from text \"{p_oStationInfo.station_group}\".", l_oException);
}
}
@ -310,24 +312,19 @@ namespace TINK.Model.Connector
/// </summary>
/// <param name="p_strGps">Text to extract positon from.</param>
/// <returns>Position object.</returns>
public static Station.Position GetPosition(string p_strGps)
public static Station.Position GetPosition(GpsInfo gps)
{
if (p_strGps == null)
if (gps == null)
{
return null;
}
var l_oPosition = p_strGps.Split(',');
if (l_oPosition.Length != 2)
return null;
double l_oLatitude;
if (!double.TryParse(l_oPosition[0], NumberStyles.Float, CultureInfo.InvariantCulture, out l_oLatitude))
if (!double.TryParse(gps.latitude, NumberStyles.Float, CultureInfo.InvariantCulture, out l_oLatitude))
return null;
double l_oLongitude;
if (!double.TryParse(l_oPosition[1], NumberStyles.Float, CultureInfo.InvariantCulture, out l_oLongitude))
if (!double.TryParse(gps.longitude, NumberStyles.Float, CultureInfo.InvariantCulture, out l_oLongitude))
return null;
return new Station.Position(l_oLatitude, l_oLongitude);
@ -354,5 +351,15 @@ namespace TINK.Model.Connector
? new Uri($"{bikeInfo.uri_operator}/{CopriServerUriList.REST_RESOURCE_ROOT}")
: null;
}
/// <summary> Gets the copriversion from.</summary>
/// <param name="response">Response to get version info from.</param>
/// <returns>COPRI version</returns>
public static Version GetCopriVersion(this CopriVersion response)
=> response!= null
&& !string.IsNullOrEmpty(response.copri_version)
&& Version.TryParse(response.copri_version, out Version copriVersion)
? copriVersion
: throw new InvalidResponseException($"Can not get version info from copri response {response?.copri_version}.");
}
}

View file

@ -1,11 +1,11 @@
using System;
using TINK.Model.Bike;
using TINK.Model.Station;
using TINK.Model.Repository.Response;
using TINK.Repository.Response;
using TINK.Model.User.Account;
using System.Collections.Generic;
using TINK.Model.State;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using Serilog;
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
@ -190,8 +190,8 @@ namespace TINK.Model.Connector
string p_strMail,
Func<DateTime> p_oDateTimeProvider)
{
var l_oBikesDictionary = new Dictionary<int, BikeInfo>();
var l_oDuplicates = new Dictionary<int, BikeInfo>();
var l_oBikesDictionary = new Dictionary<string, BikeInfo>();
var l_oDuplicates = new Dictionary<string, BikeInfo>();
// Get bikes from Copri/ file/ memory, ....
if (p_oBikesAvailableResponse != null
@ -295,7 +295,7 @@ namespace TINK.Model.Connector
return null;
}
if (bikeInfo.station == null)
if (string.IsNullOrEmpty(bikeInfo.station))
{
// Bike available must always have a station id because bikes can only be returned at a station.
Log.Error($"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. No station info set.");
@ -485,7 +485,11 @@ namespace TINK.Model.Connector
return new Bikes.Bike.TariffDescription
{
Name = tariffDesciption?.name,
#if USCSHARP9
Number = int.TryParse(tariffDesciption?.number, out int number) ? number : null,
#else
Number = int.TryParse(tariffDesciption?.number, out int number) ? number : (int?) null,
#endif
FreeTimePerSession = double.TryParse(tariffDesciption?.free_hours, NumberStyles.Any, CultureInfo.InvariantCulture, out double freeHours) ? TimeSpan.FromHours(freeHours) : TimeSpan.Zero,
FeeEuroPerHour = double.TryParse(tariffDesciption?.eur_per_hour, NumberStyles.Any, CultureInfo.InvariantCulture, out double euroPerHour) ? euroPerHour : double.NaN,
AboEuroPerMonth = double.TryParse(tariffDesciption?.abo_eur_per_month, NumberStyles.Any, CultureInfo.InvariantCulture, out double aboEuroPerMonth) ? aboEuroPerMonth : double.NaN,

View file

@ -0,0 +1,21 @@
namespace TINK.Model.Device
{
public interface ISmartDevice
{
/// <summary> Gets unitque device identifier. </summary>
/// <returns>Gets the identifies specifying device.</returns>
string Identifier { get; }
/// <summary> Manufacturer (Samsung). </summary>
string Manufacturer { get; }
/// <summary> Device Model (SMG-950U, iPhone10,6). </summary>
string Model { get; }
/// <summary> Platform (Android). </summary>
string PlatformText { get; }
/// <summary> Operating System Version Number (7.0) as text</summary>
string VersionText { get; }
}
}

View file

@ -1,16 +1,16 @@
using Plugin.Permissions.Abstractions;
using Serilog.Events;
using Serilog.Events;
using System;
using System.Threading;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.CopriApi.ServerUris;
using TINK.Model.Services.Geolocation;
using TINK.Settings;
using TINK.ViewModel.Map;
using TINK.ViewModel.Settings;
using TINK.Services;
using TINK.Model.Station;
using System.Collections.Generic;
namespace TINK.Model
{
@ -35,7 +35,7 @@ namespace TINK.Model
IFilteredConnector GetConnector(bool isConnected);
/// <summary> Name of the station which is selected. </summary>
int? SelectedStation { get; set; }
IStation SelectedStation { get; set; }
/// <summary>Polling periode.</summary>
PollingParameters Polling { get; set; }
@ -67,6 +67,10 @@ namespace TINK.Model
/// <summary> Gets the minimum logging level. </summary>
LogEventLevel MinimumLogEventLevel { get; set; }
/// <summary> Gets a value indicating whether reporting level is verbose or not.</summary>
bool IsReportLevelVerbose { get; set; }
/// <summary> Updates logging level. </summary>
/// <param name="p_oNewLevel">New level to set.</param>
void UpdateLoggingLevel(LogEventLevel p_oNewLevel);
@ -77,22 +81,19 @@ namespace TINK.Model
/// <summary> Holds the different lock service implementations.</summary>
LocksServicesContainerMutable LocksServices { get; }
/// <summary> Holds the different geo location service implementations.</summary>
ServicesContainerMutable<IGeolocation> GeolocationServices { get; }
/// <summary> Holds available app themes.</summary>
ServicesContainerMutable<object> Themes { get; }
/// <summary> Reference of object which provides device information. </summary>
IDevice Device { get; }
/// <summary> Os permission.</summary>
IPermissions Permissions { get; }
ISmartDevice SmartDevice { get; }
/// <summary> Holds the folder where settings files are stored. </summary>
string SettingsFileFolder { get; }
/// <summary> Holds the external path. </summary>
string ExternalFolder { get; }
/// <summary> Holds the stations availalbe. </summary>
IEnumerable<IStation> Stations {get; set;}
}
}

View file

@ -1,6 +1,6 @@
using Serilog;
using System;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
namespace TINK.Model.Logging
{

View file

@ -18,61 +18,61 @@ namespace TINK.Model.Logging
public static class LoggerConfigurationHelper
{
/// <summary> Holds the log file name. </summary>
private static ILoggingDirectoryManager m_oDirectoryManager = new EmptyDirectoryLoggingManger();
private static ILoggingDirectoryManager DirectoryManager { get; set; } = new EmptyDirectoryLoggingManger();
/// <summary> Sets up logging to file.</summary>
/// <param name="p_oLoggerConfiguration">Object to set up logging with.</param>
/// <param name="loggerConfiguration">Object to set up logging with.</param>
/// <param name="p_oDevice">Object to get file informaton from.</param>
/// <param name="p_oRollingInterval">Specifies rolling type.</param>
/// <param name="p_iRetainedFilesCountLimit">Count of file being retained.</param>
/// <param name="rollingInterval">Specifies rolling type.</param>
/// <param name="retainedFilesCountLimit">Count of file being retained.</param>
/// <returns>Logger object.</returns>
public static LoggerConfiguration File(
this LoggerSinkConfiguration p_oLoggerConfiguration,
string p_strLogFileFolder,
RollingInterval p_oRollingInterval = RollingInterval.Session,
int p_iRetainedFilesCountLimit = 10)
this LoggerSinkConfiguration loggerConfiguration,
string logFileFolder,
RollingInterval rollingInterval = RollingInterval.Session,
int retainedFilesCountLimit = 10)
{
if (m_oDirectoryManager is EmptyDirectoryLoggingManger)
if (DirectoryManager is EmptyDirectoryLoggingManger)
{
// Roll file only once per app session.
try
{
m_oDirectoryManager = new LoggingDirectoryManager(
DirectoryManager = new LoggingDirectoryManager(
Directory.GetFiles,
Directory.Exists,
(path) => Directory.CreateDirectory(path),
System.IO.File.Delete,
p_strLogFileFolder,
logFileFolder,
Path.DirectorySeparatorChar,
p_iRetainedFilesCountLimit);
retainedFilesCountLimit);
}
catch (Exception l_oException)
{
Log.Error("Log directory manager could not be instanciated successfully. {@l_oException}", l_oException);
m_oDirectoryManager = new EmptyDirectoryLoggingManger();
DirectoryManager = new EmptyDirectoryLoggingManger();
}
}
try
{
m_oDirectoryManager.DeleteObsoleteLogs();
DirectoryManager.DeleteObsoleteLogs();
}
catch (Exception l_oException)
{
Log.Error("Not all obsolte log files could be deleted successfully. {@l_oException}", l_oException);
}
if (p_oLoggerConfiguration == null)
if (loggerConfiguration == null)
{
return null;
}
return p_oLoggerConfiguration.File(
return loggerConfiguration.File(
new JsonFormatter(),
m_oDirectoryManager.LogFileName,
DirectoryManager.LogFileName,
/*shared: true, // Leads to exception if activated.*/
rollingInterval: Serilog.RollingInterval.Infinite,
retainedFileCountLimit: p_iRetainedFilesCountLimit);
retainedFileCountLimit: retainedFilesCountLimit);
}
/// <summary> Gets all log files in logging directory. </summary>
@ -82,7 +82,7 @@ namespace TINK.Model.Logging
{
try
{
return m_oDirectoryManager.GetLogFiles();
return DirectoryManager.GetLogFiles();
}
catch (Exception l_oException)
{
@ -97,7 +97,7 @@ namespace TINK.Model.Logging
public static string GetLogFilePath(
this ILogger p_oLogger)
{
return m_oDirectoryManager.LogFilePath;
return DirectoryManager.LogFilePath;
}
}
}

View file

@ -59,7 +59,7 @@ namespace TINK.Model.Logging
if (string.IsNullOrEmpty(LogFileTitle))
{
LogFileTitle = $"{ DateTime.Now:yyyy_MM_dd_HH_mm_ss}.jsnl";
LogFileTitle = $"{DateTime.Now:yyyy_MM_dd_HH_mm_ss}.jsnl";
}
// Create directory if direcotry does not exist.
@ -74,7 +74,6 @@ namespace TINK.Model.Logging
throw new FileOperationException($"Logging directory {LogFilePath} could not be created successfully.", l_oException);
}
}
}

View file

@ -34,6 +34,9 @@ namespace TINK.Model.Settings
/// <summary> Key of the logging level entry. </summary>
public const string MINLOGGINGLEVELKEY = "MinimumLoggingLevel";
/// <summary> Key of the logging level entry. </summary>
public const string ISREPORTLEVELVERBOSEKEY = "IsReportLevelVerbose";
/// <summary> Key of the center to ... entry. </summary>
public const string CENTERMAPTOCURRENTLOCATION = "CenterMapToCurrentLocation";
@ -273,6 +276,16 @@ namespace TINK.Model.Settings
return (LogEventLevel)int.Parse(JsonConvert.DeserializeObject<string>(l_strLevel));
}
/// <summary> Gets a value indicating whether report level is verbose or not.</summary>
/// <param name="settingsJSON">Dictionary to get value from.</param>
public static bool? GetIsReportLevelVerbose(Dictionary<string, string> settingsJSON) => GetNullableEntry<bool>(ISREPORTLEVELVERBOSEKEY, settingsJSON);
/// <summary> Sets a value indicating whether report level is verbose or not.</summary>
/// <param name="p_oSettingsJSON">Dictionary to get value from.</param>
public static Dictionary<string, string> SetIsReportLevelVerbose(this IDictionary<string, string> targetDictionary, bool isReportLevelVerbose)
=> SetEntry(isReportLevelVerbose, ISREPORTLEVELVERBOSEKEY, targetDictionary);
/// <summary> Sets the logging level.</summary>
/// <param name="p_oSettingsJSON">Dictionary to get logging level from.</param>
public static Dictionary<string, string> SetMinimumLoggingLevel(this IDictionary<string, string> p_oTargetDictionary, LogEventLevel p_oLevel)

View file

@ -15,6 +15,8 @@ namespace TINK.Model.Settings
{
public const LogEventLevel DEFAULTLOGGINLEVEL = LogEventLevel.Error;
public const bool DEFAULTREPOTLEVEL = false;
// Default value of the expires after entry. Controls the expiration time of the cache values.
private TimeSpan DEFAULTEXPIRESAFTER = TimeSpan.FromSeconds(1);
@ -24,6 +26,7 @@ namespace TINK.Model.Settings
/// <param name="activeUri"></param>
/// <param name="pollingParameters"></param>
/// <param name="minimumLogEventLevel">Minimum logging level to be applied.</param>
/// <param name="isReportLevelVerbose">True if logging level is verbose.</param>
/// <param name="expiresAfter">Holds the expires after value.</param>
/// <param name="activeLockService">Gets the name of the lock service to use.</param>
/// <param name="connectTimeout">Timeout to apply when connecting to bluetooth lock</param>
@ -34,6 +37,7 @@ namespace TINK.Model.Settings
Uri activeUri = null,
PollingParameters pollingParameters = null,
LogEventLevel? minimumLogEventLevel = null,
bool? isReportLevelVerbose = null,
TimeSpan? expiresAfter = null,
string activeLockService = null,
TimeSpan? connectTimeout = null,
@ -48,6 +52,7 @@ namespace TINK.Model.Settings
ActiveUri = GetActiveUri(activeUri);
PollingParameters = pollingParameters ?? PollingParameters.Default;
MinimumLogEventLevel = minimumLogEventLevel ?? DEFAULTLOGGINLEVEL;
IsReportLevelVerbose = isReportLevelVerbose ?? DEFAULTREPOTLEVEL;
ExpiresAfter = expiresAfter ?? DEFAULTEXPIRESAFTER;
ActiveLockService = activeLockService ?? LocksServicesContainerMutable.DefaultLocksservice;
ConnectTimeout = connectTimeout ?? new TimeSpan(0, 0, TimeOutProvider.DEFAULT_BLUETOOTHCONNECT_TIMEOUTSECONDS); // Try one sec. to connect.
@ -93,6 +98,9 @@ namespace TINK.Model.Settings
public string ActiveTheme { get; }
/// <summary> Gets a value indicating whether reporting level is verbose or not.</summary>
public bool IsReportLevelVerbose { get; }
public static Uri GetActiveUri(Uri activeUri) => activeUri ?? Services.CopriApi.ServerUris.CopriServerUriList.DefaultActiveUri;
public static bool GetCenterMapToCurrentLocation(Uri activeUri)

View file

@ -5,7 +5,7 @@ namespace TINK.Model.Station
public interface IStation
{
/// <summary> Holds the unique id of the station.c</summary>
int Id { get; }
string Id { get; }
/// <summary> Holds the group to which the station belongs.</summary>
IEnumerable<string> Group { get; }

View file

@ -6,7 +6,7 @@ namespace TINK.Model.Station
public class NullStation : IStation
{
/// <summary> Holds the unique id of the station.c</summary>
public int Id => -1;
public string Id => null;
/// <summary> Holds the group to which the station belongs.</summary>
public IEnumerable<string> Group => new List<string>();

View file

@ -12,7 +12,7 @@ namespace TINK.Model.Station
/// <param name="p_oPosition">GPS- position of the station.</param>
/// <param name="p_strStationName">Name of the station.</param>
public Station(
int p_iId,
string p_iId,
IEnumerable<string> p_oGroup,
Position p_oPosition,
string p_strStationName = "")
@ -24,7 +24,7 @@ namespace TINK.Model.Station
}
/// <summary> Holds the unique id of the station.c</summary>
public int Id { get; }
public string Id { get; }
/// <summary> Holds the group to which the station belongs.</summary>
public IEnumerable<string> Group { get; }

View file

@ -4,10 +4,10 @@ using System.Collections.Generic;
namespace TINK.Model.Station
{
public class StationDictionary : IEnumerable<Station>
public class StationDictionary : IEnumerable<IStation>
{
/// <summary> Holds the list of stations. </summary>
private readonly IDictionary<int, Station> m_oStationDictionary;
private readonly IDictionary<string, IStation> m_oStationDictionary;
/// <summary> Count of stations. </summary>
public int Count { get { return m_oStationDictionary.Count; } }
@ -16,16 +16,16 @@ namespace TINK.Model.Station
/// <summary> Constructs a station dictionary object. </summary>
/// <param name="p_oVersion">Version of copri- service.</param>
public StationDictionary(Version p_oVersion = null, IDictionary<int, Station> p_oStations = null)
public StationDictionary(Version p_oVersion = null, IDictionary<string, IStation> p_oStations = null)
{
m_oStationDictionary = p_oStations ?? new Dictionary<int, Station>();
m_oStationDictionary = p_oStations ?? new Dictionary<string, IStation>();
CopriVersion = p_oVersion != null
? new Version(p_oVersion.Major, p_oVersion.Minor, p_oVersion.Revision, p_oVersion.Build)
: new Version(0, 0, 0, 0);
}
public IEnumerator<Station> GetEnumerator()
public IEnumerator<IStation> GetEnumerator()
{
return m_oStationDictionary.Values.GetEnumerator();
}
@ -33,41 +33,41 @@ namespace TINK.Model.Station
/// <summary>
/// Deteermines whether a station by given key exists.
/// </summary>
/// <param name="p_strKey">Key to check.</param>
/// <param name="key">Key to check.</param>
/// <returns>True if station exists.</returns>
public bool ContainsKey(int p_strKey)
public bool ContainsKey(string key)
{
return m_oStationDictionary.ContainsKey(p_strKey);
return m_oStationDictionary.ContainsKey(key);
}
/// <summary>
/// Remove a station by station id.
/// </summary>
/// <param name="p_iId"></param>
public void RemoveById(int p_iId)
/// <param name="id"></param>
public void RemoveById(string id)
{
if (!m_oStationDictionary.ContainsKey(p_iId))
if (!m_oStationDictionary.ContainsKey(id))
{
// Nothing to do if there is no station with given name.
return;
}
m_oStationDictionary.Remove(p_iId);
m_oStationDictionary.Remove(id);
}
/// <summary>
/// Remove a station by station name.
/// </summary>
/// <param name="p_iId"></param>
public Station GetById(int p_iId)
/// <param name="id"></param>
public IStation GetById(string id)
{
if (!m_oStationDictionary.ContainsKey(p_iId))
if (!m_oStationDictionary.ContainsKey(id))
{
// Nothing to do if there is no station with given name.
return null;
}
return m_oStationDictionary[p_iId];
return m_oStationDictionary[id];
}
/// <summary>

View file

@ -22,6 +22,7 @@ using TINK.ViewModel.Settings;
using TINK.Services;
using TINK.Services.BluetoothLock.BLE;
using Xamarin.Forms;
using TINK.Model.Station;
namespace TINK.Model
{
@ -34,7 +35,7 @@ namespace TINK.Model
/// <returns>True if setting credentials succeeded.</returns>
public delegate bool SetCredentialsDelegate(string p_strMailAddress, string p_strPassword);
/// <summary>Returns the id of the app to be identified by copri.</summary>
/// <summary>Returns the id of the app (sharee.bike) to be identified by copri.</summary>
public static string MerchantId => "oiF2kahH";
/// <summary>
@ -67,6 +68,9 @@ namespace TINK.Model
/// <summary> Gets the minimum logging level. </summary>
public LogEventLevel MinimumLogEventLevel { get; set; }
/// <summary> Gets a value indicating whether reporting level is verbose or not.</summary>
public bool IsReportLevelVerbose { get; set; }
/// <summary> Holds the uri which is applied after restart. </summary>
public Uri NextActiveUri { get; set; }
@ -81,6 +85,7 @@ namespace TINK.Model
.SetGroupFilterSettings(FilterGroupSetting)
.SetAppVersion(AppVersion)
.SetMinimumLoggingLevel(MinimumLogEventLevel)
.SetIsReportLevelVerbose(IsReportLevelVerbose)
.SetExpiresAfter(ExpiresAfter)
.SetWhatsNew(AppVersion)
.SetActiveLockService(LocksServices.Active.GetType().FullName)
@ -115,7 +120,11 @@ namespace TINK.Model
/// <summary>
/// Holds the default polling value.
/// </summary>
#if USCSHARP9
public TimeSpan DefaultPolling => new (0, 0, 10);
#else
public TimeSpan DefaultPolling => new TimeSpan(0, 0, 10);
#endif
/// <summary> Constructs TinkApp object. </summary>
/// <param name="settings"></param>
@ -139,10 +148,9 @@ namespace TINK.Model
Settings.Settings settings,
IStore accountStore,
Func<bool, Uri, string, string, TimeSpan, IConnector> connectorFactory,
IGeolocation geolocationService,
IGeolodationDependent geolodationServiceDependent,
IServicesContainer<IGeolocation> geolocationServicesContainer,
ILocksService locksService,
IDevice device,
ISmartDevice device,
ISpecialFolder specialFolder,
ICipher cipher,
IPermissions permissions = null,
@ -188,16 +196,8 @@ namespace TINK.Model
new HashSet<object> { new Themes.Konrad() , new Themes.ShareeBike() },
settings.ActiveTheme);
GeolocationServices = new ServicesContainerMutable<IGeolocation>(
geolocationService == null
? new HashSet<IGeolocation> { new LastKnownGeolocationService(geolodationServiceDependent), new SimulatedGeolocationService(geolodationServiceDependent), new GeolocationService(geolodationServiceDependent) }
: new HashSet<IGeolocation> { geolocationService },
geolocationService == null
? (lastVersion >= new Version(3, 0, 173) ? settings.ActiveGeolocationService : typeof(LastKnownGeolocationService).FullName)
: geolocationService.GetType().FullName);
// Load filters from settings or apply defaults if no settings are available
var l_oAccount = accountStore.Load();
GeolocationServices = geolocationServicesContainer
?? throw new ArgumentException($"Can not instantiate {nameof(TinkApp)}- object. No geolocation services container object available.");
if (settings.ActiveUri == new Uri(CopriServerUriList.TINK_LIVE) ||
settings.ActiveUri == new Uri(CopriServerUriList.TINK_DEVEL))
@ -217,7 +217,7 @@ namespace TINK.Model
CenterMapToCurrentLocation = settings.CenterMapToCurrentLocation;
Device = device
SmartDevice = device
?? throw new ArgumentException("Can not instantiate TinkApp- object. No device information provider available.");
if (specialFolder == null)
@ -236,12 +236,10 @@ namespace TINK.Model
SettingsFileFolder = specialFolder.GetInternalPersonalDir();
SelectedStation = null;
ActiveUser = new User.User(
accountStore,
l_oAccount,
device.GetIdentifier());
accountStore.GetType().Name == "StoreLegacy" ? new Store() : accountStore,
accountStore.Load().Result,
device.Identifier);
this.isConnectedFunc = isConnectedFunc ?? (() => CrossConnectivity.Current.IsConnected);
@ -249,7 +247,7 @@ namespace TINK.Model
// Create filtered connector for offline mode.
m_oConnector = FilteredConnectorFactory.Create(
FilterGroupSetting.DoFilter(l_oAccount.DoFilter(GroupFilterMapPage.DoFilter())),
FilterGroupSetting.DoFilter(GroupFilterMapPage.DoFilter()),
ConnectorFactory(GetIsConnected(), settings.ActiveUri, ActiveUser.SessionCookie, ActiveUser.Mail, ExpiresAfter));
// Get uris from file.
@ -266,8 +264,7 @@ namespace TINK.Model
MinimumLogEventLevel = settings.MinimumLogEventLevel;
Permissions = permissions ??
throw new ArgumentException("Can not instantiate TinkApp- object. Permissions object must never be null.");
IsReportLevelVerbose = settings.IsReportLevelVerbose;
WhatsNew = new WhatsNew(AppVersion, lastVersion, whatsNewShownInVersion);
@ -299,13 +296,10 @@ namespace TINK.Model
public User.User ActiveUser { get; }
/// <summary> Reference of object which provides device information. </summary>
public IDevice Device { get; }
/// <summary> Os permission.</summary>
public IPermissions Permissions { get; }
public ISmartDevice SmartDevice { get; }
/// <summary> Holds delegate to determine whether device is connected or not.</summary>
private Func<bool> isConnectedFunc;
private readonly Func<bool> isConnectedFunc;
/// <summary> Gets whether device is connected to internet or not. </summary>
public bool GetIsConnected() => isConnectedFunc();
@ -328,13 +322,16 @@ namespace TINK.Model
public ICipher Cipher { get; }
/// <summary> Name of the station which is selected. </summary>
public int? SelectedStation { get; set; }
public IStation SelectedStation { get; set; } = new Station.Station(null, new List<string>(), null);
/// <summary> Holds the stations availalbe. </summary>
public IEnumerable<IStation> Stations { get; set; } = new List<Station.Station>();
/// <summary> Action to post to GUI thread.</summary>
public Action<SendOrPostCallback, object> PostAction { get; }
/// <summary> Function which creates a connector depending on connected status.</summary>
private Func<bool, Uri, string, string, TimeSpan, IConnector> ConnectorFactory { get; }
private Func<bool, Uri, string /*userAgent*/, string /*sessionCookie*/, TimeSpan, IConnector> ConnectorFactory { get; }
/// <summary> Holds the object which provides offline data.</summary>
private IFilteredConnector m_oConnector;
@ -362,14 +359,11 @@ namespace TINK.Model
return m_oConnector;
}
/// <summary> Query geolocation. </summary>
public IGeolocation Geolocation => GeolocationServices.Active;
/// <summary> Manages the different types of LocksService objects.</summary>
public LocksServicesContainerMutable LocksServices { get; set; }
/// <summary> Holds available app themes.</summary>
public ServicesContainerMutable<IGeolocation> GeolocationServices { get; }
public IServicesContainer<IGeolocation> GeolocationServices { get; }
/// <summary> Manages the different types of LocksService objects.</summary>
public ServicesContainerMutable<object> Themes { get; }

View file

@ -17,6 +17,7 @@ namespace TINK.Model.User.Account
PickLoggingLevel = 64, // Allows to select the logging level.
ShowDiagnostics = 128, // Turns on display of diagnostics.
SwitchNoSiteCaching = 1024, // Allows to turn off/ on caching of sites displayed in app hosted by COPRI
ReportLevel = 2048, // Allows extent to show error messages.
All = PickCopriServer +
ManageCopriCacheExpiration +
ManagePolling +
@ -24,7 +25,8 @@ namespace TINK.Model.User.Account
PickLocationServiceImplementation +
PickLoggingLevel +
ShowDiagnostics +
SwitchNoSiteCaching,
SwitchNoSiteCaching +
ReportLevel,
}
/// <summary>
@ -48,24 +50,24 @@ namespace TINK.Model.User.Account
public class Account : IAccount
{
/// <summary> Constructs an account object.</summary>
/// <param name="p_oMail">Mail addresss.</param>
/// <param name="p_Pwd">Password.</param>
/// <param name="p_oSessionCookie">Session cookie from copri.</param>
/// <param name="p_strGroup">Group holdig info about Group (TINK, Konrad, ...)</param>
/// <param name="mail">Mail addresss.</param>
/// <param name="password">Password.</param>
/// <param name="sessionCookie">Session cookie from copri.</param>
/// <param name="group">Group holdig info about Group (TINK, Konrad, ...)</param>
/// <param name="p_iDebugLevel">Flag which controls display of debug settings.</param>
public Account(
string p_oMail,
string p_Pwd,
string p_oSessionCookie,
IEnumerable<string> p_strGroup,
string mail,
string password,
string sessionCookie,
IEnumerable<string> group,
Permissions debugLevel = Permissions.None)
{
Mail = p_oMail;
Pwd = p_Pwd;
SessionCookie = p_oSessionCookie;
Mail = mail;
Pwd = password;
SessionCookie = sessionCookie;
DebugLevel = debugLevel;
Group = p_strGroup != null
? new HashSet<string>(p_strGroup).ToList()
Group = group != null
? new HashSet<string>(group).ToList()
: throw new ArgumentException("Can not instantiate account object. Reference to group list must not be empty.");
}

View file

@ -1,26 +1,26 @@
namespace TINK.Model.User.Account
using System.Threading.Tasks;
namespace TINK.Model.User.Account
{
/// <summary>
/// Interface to manage an account store.
/// </summary>
/// <summary>Interface to manage an account store.</summary>
public interface IStore
{
/// <summary>
/// Reads mail address and password from account store.
/// </summary>
/// <returns></returns>
IAccount Load();
Task<IAccount> Load();
/// <summary>
/// Writes mail address and password to account store.
/// </summary>
/// <param name="p_oMailAndPwd"></param>
void Save(IAccount p_oMailAndPwd);
/// <param name="mailAndPwd"></param>
Task Save(IAccount mailAndPwd);
/// <summary>
/// Deletes mail address and password from account store.
/// </summary>
/// <returns> Empty account instance if deleting succeeded.</returns>
IAccount Delete(IAccount p_oMailAndPwd);
IAccount Delete(IAccount mailAndPwd);
}
}

View file

@ -0,0 +1,67 @@
using Serilog;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Essentials;
namespace TINK.Model.User.Account
{
public class Store : IStore
{
/// <summary> Holds id of the debug level key. </summary>
private const string KEY_DEBUGLEVEL = "DebugLevel";
/// <summary> Holds the id of the session. </summary>
private const string KEY_SESSIONCOOKIE = "SessionCookie";
/// <summary> Holds id of the mail address key. </summary>
private const string KEY_MAILADDRESS = "MailAddress";
public IAccount Delete(IAccount account)
{
SecureStorage.RemoveAll();
return new EmptyAccount();
}
public async Task<IAccount> Load()
{
var mail = string.Empty;
var sessionCookie = string.Empty;
var debugLevel = Permissions.None;
try
{
mail = await SecureStorage.GetAsync(KEY_MAILADDRESS);
sessionCookie = await SecureStorage.GetAsync(KEY_SESSIONCOOKIE);
Enum.TryParse(await SecureStorage.GetAsync(KEY_DEBUGLEVEL), out debugLevel);
}
catch (Exception exception)
{
Log.ForContext<Store>().Error("Loading account from store failed. {Exception}", exception);
}
return new Account(
mail,
string.Empty,
sessionCookie,
new List<string>(),
debugLevel);
}
public async Task Save(IAccount mailAndPwd)
{
try
{
await SecureStorage.SetAsync(KEY_MAILADDRESS, mailAndPwd.Mail);
await SecureStorage.SetAsync(KEY_SESSIONCOOKIE, mailAndPwd.SessionCookie);
await SecureStorage.SetAsync(KEY_DEBUGLEVEL, mailAndPwd.DebugLevel.ToString());
}
catch (Exception exception)
{
Log.ForContext<Store>().Error("Saving account from store failed. {Exception}", exception);
}
}
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TINK.Model.User.Account;
namespace TINK.Model.User
@ -29,14 +30,14 @@ namespace TINK.Model.User
/// </summary>
/// <param name="p_oAccountStore"> Object to use for loading and saving user data.</param>
public User(
IStore p_oAccountStore,
IAccount p_oAccount,
string p_strDeviceId)
IStore accountStore,
IAccount account,
string deviceId)
{
m_oStore = p_oAccountStore
m_oStore = accountStore
?? throw new ArgumentException("Can not instantiate user- object. No store functionality available.");
DeviceId = p_strDeviceId;
m_oAccount = new AccountMutable(p_oAccount);
DeviceId = deviceId;
m_oAccount = new AccountMutable(account);
}
/// <summary> Is fired wheneverlogin state changes. </summary>
@ -109,13 +110,13 @@ namespace TINK.Model.User
/// <param name="p_oAccount">Account to use for login.</param>
/// <param name="p_str_DeviceId">Holds the Id to identify the device.</param>
/// <param name="isConnected">True if connector has access to copri server, false if cached values are used.</param>
public void Login(IAccount account)
public async Task Login(IAccount account)
{
// Update account instance from copri data.
m_oAccount.Copy(account);
// Save data to store.
m_oStore.Save(m_oAccount);
await m_oStore.Save(m_oAccount);
// Nothing to do because state did not change.
StateChanged?.Invoke(this, new EventArgs());

View file

@ -316,7 +316,7 @@ namespace TINK.Model
},
{
new Version(3, 0, 208),
AppResources.ChangeLog3_0_208
AppResources.ChangeLog3_0_208 // Minor fixes.
},
{
new Version(3, 0, 209),
@ -340,7 +340,7 @@ namespace TINK.Model
},
{
new Version(3, 0, 218),
AppResources.ChangeLog3_0_218
AppResources.ChangeLog3_0_208
},
{
new Version(3, 0, 219),
@ -353,8 +353,61 @@ namespace TINK.Model
{
new Version(3, 0, 222),
AppResources.ChangeLog3_0_222
},
{
new Version(3, 0, 223),
AppResources.ChangeLog3_0_208
},
{
new Version(3, 0, 224),
AppResources.ChangeLog3_0_224
},
{
new Version(3, 0, 225),
AppResources.ChangeLog3_0_208
},
{
new Version(3, 0, 226),
AppResources.ChangeLog3_0_226
},
{
new Version(3, 0, 227),
AppResources.ChangeLog3_0_227
},
{
new Version(3, 0, 228),
AppResources.ChangeLog3_0_208
},
{
new Version(3, 0, 231),
AppResources.ChangeLog3_0_231
},
{
new Version(3, 0, 232),
AppResources.ChangeLog3_0_232
},
{
new Version(3, 0, 234),
AppResources.ChangeLog3_0_234
},
{
new Version(3, 0, 235),
AppResources.ChangeLog3_0_235
},
{
new Version(3, 0, 236),
AppResources.ChangeLog3_0_236
},
{
new Version(3, 0, 237),
AppResources.ChangeLog3_0_237
},
{
new Version(3, 0, 238),
AppResources.ChangeLog3_0_231
}
};
/// <summary> Manges the whats new information.</summary>