mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2025-04-20 03:56:29 +02:00
Initial version.
This commit is contained in:
parent
193aaa1a56
commit
b72c67a53e
228 changed files with 25924 additions and 0 deletions
14
TINKLib/CSharp9.cs
Normal file
14
TINKLib/CSharp9.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserved to be used by the compiler for tracking metadata.
|
||||
/// This class should not be used by developers in source code.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal static class IsExternalInit
|
||||
{
|
||||
}
|
||||
}
|
216
TINKLib/Model/Bikes/Bike/BC/BikeInfo.cs
Normal file
216
TINKLib/Model/Bikes/Bike/BC/BikeInfo.cs
Normal file
|
@ -0,0 +1,216 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TINK.Model.Bikes.Bike;
|
||||
using TINK.Model.State;
|
||||
|
||||
namespace TINK.Model.Bike.BC
|
||||
{
|
||||
public class BikeInfo : IBikeInfo
|
||||
{
|
||||
/// <summary> Default value of demo property. </summary>
|
||||
public const bool DEFAULTVALUEISDEMO = false;
|
||||
|
||||
/// <summary> Holds the info about the bike state. </summary>
|
||||
private readonly IStateInfo m_oStateInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the bike object.
|
||||
/// </summary>
|
||||
private Bike Bike { get; }
|
||||
|
||||
/// <summary> Constructs a bike object.</summary>
|
||||
protected BikeInfo(
|
||||
IStateInfo stateInfo,
|
||||
int id,
|
||||
bool? isDemo = DEFAULTVALUEISDEMO,
|
||||
IEnumerable<string> group = null,
|
||||
WheelType? wheelType = null,
|
||||
TypeOfBike? typeOfBike = null,
|
||||
string description = null,
|
||||
int? currentStationId = null,
|
||||
Uri operatorUri = null,
|
||||
TariffDescription tariffDescription = null)
|
||||
{
|
||||
Bike = new Bike(id, wheelType, typeOfBike, description);
|
||||
|
||||
m_oStateInfo = stateInfo;
|
||||
|
||||
IsDemo = isDemo ?? DEFAULTVALUEISDEMO;
|
||||
Group = group ?? new List<string>();
|
||||
CurrentStation = currentStationId;
|
||||
OperatorUri = operatorUri;
|
||||
TariffDescription = tariffDescription;
|
||||
}
|
||||
|
||||
public BikeInfo(BikeInfo bikeInfo) : this(
|
||||
bikeInfo?.State,
|
||||
bikeInfo?.Id ?? throw new ArgumentException($"Can not copy-construct {typeof(BikeInfo).Name}-object. Source must not be null."),
|
||||
bikeInfo.IsDemo,
|
||||
bikeInfo.Group,
|
||||
bikeInfo.WheelType,
|
||||
bikeInfo.TypeOfBike,
|
||||
bikeInfo.Description,
|
||||
bikeInfo.CurrentStation,
|
||||
bikeInfo.OperatorUri,
|
||||
bikeInfo.TariffDescription) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a bike info object for a available bike.
|
||||
/// </summary>
|
||||
/// <param name="id">Unique id of bike.</param>
|
||||
/// <param name="currentStationId">Id of station where bike is located.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="tariffDescription">Hold tariff description of bike.</param>
|
||||
/// <param name="wheelType"></param>
|
||||
public BikeInfo(
|
||||
int id,
|
||||
int? currentStationId,
|
||||
Uri operatorUri = null,
|
||||
TariffDescription tariffDescription = null,
|
||||
bool? isDemo = DEFAULTVALUEISDEMO,
|
||||
IEnumerable<string> group = null,
|
||||
WheelType? wheelType = null,
|
||||
TypeOfBike? typeOfBike = null,
|
||||
string description = null) : this(
|
||||
new StateInfo(),
|
||||
id,
|
||||
isDemo,
|
||||
group,
|
||||
wheelType,
|
||||
typeOfBike,
|
||||
description,
|
||||
currentStationId,
|
||||
operatorUri,
|
||||
tariffDescription)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a bike info object for a requested bike.
|
||||
/// </summary>
|
||||
/// <param name="dateTimeProvider">Provider for current date time to calculate remainig time on demand for state of type reserved.</param>
|
||||
/// <param name="wheelType"></param>
|
||||
/// <param name="id">Unique id of bike.</param>
|
||||
/// <param name="stationId">Name of station where bike is located, null if bike is on the road.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="tariffDescription">Hold tariff description of bike.</param>
|
||||
/// <param name="requestedAt">Date time when bike was requested</param>
|
||||
/// <param name="mailAddress">Mail address of user which requested bike.</param>
|
||||
/// <param name="code">Booking code.</param>
|
||||
/// <param name="p_oDateTimeNowProvider">Date time provider to calculate reaining time.</param>
|
||||
public BikeInfo(
|
||||
int id,
|
||||
bool? isDemo,
|
||||
IEnumerable<string> group,
|
||||
WheelType? wheelType,
|
||||
TypeOfBike? typeOfBike,
|
||||
string description,
|
||||
int? stationId,
|
||||
Uri operatorUri,
|
||||
TariffDescription tariffDescription,
|
||||
DateTime requestedAt,
|
||||
string mailAddress,
|
||||
string code,
|
||||
Func<DateTime> dateTimeProvider = null) : this(
|
||||
new StateInfo(
|
||||
dateTimeProvider,
|
||||
requestedAt,
|
||||
mailAddress,
|
||||
code),
|
||||
id,
|
||||
isDemo,
|
||||
group,
|
||||
wheelType,
|
||||
typeOfBike,
|
||||
description,
|
||||
stationId,
|
||||
operatorUri,
|
||||
tariffDescription)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a bike info object for a booked bike.
|
||||
/// </summary>
|
||||
/// <param name="dateTimeProvider">Provider for current date time to calculate remainig time on demand for state of type reserved.</param>
|
||||
/// <param name="wheelType"></param>
|
||||
/// <param name="id">Unique id of bike.</param>
|
||||
/// <param name="currentStationId">Name of station where bike is located, null if bike is on the road.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="tariffDescription">Hold tariff description of bike.</param>
|
||||
/// <param name="bookedAt">Date time when bike was booked</param>
|
||||
/// <param name="mailAddress">Mail address of user which booked bike.</param>
|
||||
/// <param name="code">Booking code.</param>
|
||||
public BikeInfo(
|
||||
int id,
|
||||
bool? isDemo,
|
||||
IEnumerable<string> group,
|
||||
WheelType? wheelType,
|
||||
TypeOfBike? typeOfBike,
|
||||
string description,
|
||||
int? currentStationId,
|
||||
Uri operatorUri,
|
||||
TariffDescription tariffDescription,
|
||||
DateTime bookedAt,
|
||||
string mailAddress,
|
||||
string code) : this(
|
||||
new StateInfo(
|
||||
bookedAt,
|
||||
mailAddress,
|
||||
code),
|
||||
id,
|
||||
isDemo,
|
||||
group,
|
||||
wheelType,
|
||||
typeOfBike,
|
||||
description,
|
||||
currentStationId,
|
||||
operatorUri,
|
||||
tariffDescription)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary> True if device is demo device, false otherwise. </summary>
|
||||
public bool IsDemo { get; }
|
||||
|
||||
/// <summary> Returns the group (TINK, Konrad, ...). </summary>
|
||||
public IEnumerable<string> Group { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Station a which bike is located, null otherwise.
|
||||
/// </summary>
|
||||
public int? CurrentStation { get; }
|
||||
|
||||
/// <summary> Holds description about the tarif. </summary>
|
||||
public TariffDescription TariffDescription { get; }
|
||||
|
||||
|
||||
/// Holds the rent state of the bike.
|
||||
/// </summary>
|
||||
public IStateInfo State
|
||||
{
|
||||
get { return m_oStateInfo; }
|
||||
}
|
||||
|
||||
public int Id => Bike.Id;
|
||||
|
||||
public WheelType? WheelType => Bike.WheelType;
|
||||
|
||||
public TypeOfBike? TypeOfBike => Bike.TypeOfBike;
|
||||
|
||||
public string Description => Bike.Description;
|
||||
|
||||
/// <summary>
|
||||
/// Uri of the operator or null, in case of single operator setup.
|
||||
/// </summary>
|
||||
public Uri OperatorUri { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts the instance to text.
|
||||
/// </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}.";
|
||||
}
|
||||
}
|
||||
}
|
124
TINKLib/Model/Bikes/Bike/BC/BikeInfoMutable.cs
Normal file
124
TINKLib/Model/Bikes/Bike/BC/BikeInfoMutable.cs
Normal file
|
@ -0,0 +1,124 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using TINK.Model.Bikes.Bike;
|
||||
using TINK.Model.Bikes.Bike.BC;
|
||||
using TINK.Model.State;
|
||||
|
||||
namespace TINK.Model.Bike.BC
|
||||
{
|
||||
[DataContract]
|
||||
public class BikeInfoMutable : IBikeInfoMutable, INotifyPropertyChanged
|
||||
{
|
||||
/// <summary> Holds the bike. </summary>
|
||||
private readonly Bike m_oBike;
|
||||
|
||||
/// <summary> Holds the state info of the bike. </summary>
|
||||
private readonly StateInfoMutable m_oStateInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a bike.
|
||||
/// </summary>
|
||||
/// <param name="id">Unique id of bike.</param>
|
||||
/// <param name="isDemo">True if device is demo device, false otherwise.</param>
|
||||
/// <param name="dateTimeProvider">Provider for current date time to calculate remainig time on demand for state of type reserved.</param>
|
||||
/// <param name="wheelType"></param>
|
||||
/// <param name="currentStationId">Name of station where bike is located, null if bike is on the road.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="tariffDescription">Hold tariff description of bike.</param>
|
||||
/// <param name="stateInfo">Bike state info.</param>
|
||||
protected BikeInfoMutable(
|
||||
int id,
|
||||
bool isDemo = BikeInfo.DEFAULTVALUEISDEMO,
|
||||
IEnumerable<string> group = null,
|
||||
WheelType? wheelType = null,
|
||||
TypeOfBike? typeOfBike = null,
|
||||
string description = null,
|
||||
int? currentStationId = null,
|
||||
Uri operatorUri = null,
|
||||
TariffDescription tariffDescription = null,
|
||||
Func<DateTime> dateTimeProvider = null,
|
||||
IStateInfo stateInfo = null)
|
||||
{
|
||||
IsDemo = isDemo;
|
||||
Group = group;
|
||||
m_oBike = new Bike(id, wheelType, typeOfBike, description);
|
||||
m_oStateInfo = new StateInfoMutable(dateTimeProvider, stateInfo);
|
||||
m_oStateInfo.PropertyChanged += (sender, eventargs) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(eventargs.PropertyName));
|
||||
CurrentStation = currentStationId;
|
||||
OperatorUri = operatorUri;
|
||||
TariffDescription = tariffDescription;
|
||||
}
|
||||
|
||||
/// <summary> Constructs a bike object from source. </summary>
|
||||
public BikeInfoMutable(IBikeInfo p_oBike) : this(
|
||||
p_oBike.Id,
|
||||
p_oBike.IsDemo,
|
||||
p_oBike.Group,
|
||||
p_oBike.WheelType,
|
||||
p_oBike.TypeOfBike,
|
||||
p_oBike.Description,
|
||||
p_oBike.CurrentStation,
|
||||
p_oBike.OperatorUri,
|
||||
p_oBike.TariffDescription,
|
||||
null,
|
||||
p_oBike.State)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Station a which bike is located, null otherwise.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public int? CurrentStation { get; }
|
||||
|
||||
/// <summary> Holds description about the tarif. </summary>
|
||||
[DataMember]
|
||||
public TariffDescription TariffDescription { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the rent state of the bike.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public StateInfoMutable State
|
||||
{
|
||||
get { return m_oStateInfo; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uri of the operator or null, in case of single operator setup.
|
||||
/// </summary>
|
||||
public Uri OperatorUri { get; }
|
||||
|
||||
/// <summary> Unused member. </summary>
|
||||
IStateInfoMutable IBikeInfoMutable.State => m_oStateInfo;
|
||||
|
||||
public int Id => m_oBike.Id;
|
||||
|
||||
public bool IsDemo { get; }
|
||||
|
||||
/// <summary> Returns the group (TINK, Konrad, ...). </summary>
|
||||
public IEnumerable<string> Group { get; }
|
||||
|
||||
public WheelType? WheelType => m_oBike.WheelType;
|
||||
|
||||
public TypeOfBike? TypeOfBike => m_oBike.TypeOfBike;
|
||||
|
||||
public string Description => m_oBike.Description;
|
||||
|
||||
/// <summary>
|
||||
/// Fired whenever property of bike changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Converts the instance to text.
|
||||
/// </summary>
|
||||
/// <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")}.";
|
||||
}
|
||||
}
|
||||
}
|
55
TINKLib/Model/Bikes/Bike/BC/IBikeInfo.cs
Normal file
55
TINKLib/Model/Bikes/Bike/BC/IBikeInfo.cs
Normal file
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TINK.Model.Bikes.Bike;
|
||||
using TINK.Model.State;
|
||||
|
||||
namespace TINK.Model.Bike.BC
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows to access bike info.
|
||||
/// </summary>
|
||||
public interface IBikeInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the unique id of the bike;
|
||||
/// </summary>
|
||||
int Id { get; }
|
||||
|
||||
/// <summary> True if bike is a demo bike. </summary>
|
||||
bool IsDemo { get; }
|
||||
|
||||
/// <summary> Returns the group (TINK, Konrad, ...). </summary>
|
||||
IEnumerable<string> Group { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the count of wheels.
|
||||
/// </summary>
|
||||
WheelType? WheelType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the type of bike.
|
||||
/// </summary>
|
||||
TypeOfBike? TypeOfBike { get; }
|
||||
|
||||
/// <summary> Holds the description of the bike. </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Station a which bike is located, null otherwise.
|
||||
/// </summary>
|
||||
int? CurrentStation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Uri of the operator or null, in case of single operator setup.
|
||||
/// </summary>
|
||||
Uri OperatorUri { get; }
|
||||
|
||||
/// <summary> Holds description about the tarif. </summary>
|
||||
TariffDescription TariffDescription { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the rent state of the bike.
|
||||
/// </summary>
|
||||
IStateInfo State { get; }
|
||||
}
|
||||
}
|
61
TINKLib/Model/Bikes/Bike/BC/IBikeInfoMutable.cs
Normal file
61
TINKLib/Model/Bikes/Bike/BC/IBikeInfoMutable.cs
Normal file
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using TINK.Model.Bike;
|
||||
using TINK.Model.State;
|
||||
|
||||
namespace TINK.Model.Bikes.Bike.BC
|
||||
{
|
||||
public interface IBikeInfoMutable
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the unique id of the bike;
|
||||
/// </summary>
|
||||
int Id { get; }
|
||||
|
||||
/// <summary> True if bike is a demo bike. </summary>
|
||||
bool IsDemo { get; }
|
||||
|
||||
/// <summary> Returns the group (TINK, Konrad, ...). </summary>
|
||||
IEnumerable<string> Group { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the count of wheels.
|
||||
/// </summary>
|
||||
WheelType? WheelType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the type of bike.
|
||||
/// </summary>
|
||||
TypeOfBike? TypeOfBike { get; }
|
||||
|
||||
/// <summary> Holds the description of the bike. </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Station a which bike is located, null otherwise.
|
||||
/// </summary>
|
||||
int? CurrentStation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the rent state of the bike.
|
||||
/// </summary>
|
||||
IStateInfoMutable State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Uri of the operator or null, in case of single operator setup.
|
||||
/// </summary>
|
||||
Uri OperatorUri { get; }
|
||||
|
||||
event PropertyChangedEventHandler PropertyChanged;
|
||||
}
|
||||
|
||||
public enum NotifyPropertyChangedLevel
|
||||
{
|
||||
/// <summary> Notify about all property changes.</summary>
|
||||
All,
|
||||
|
||||
/// <summary> Notify about no property changes.</summary>
|
||||
None
|
||||
}
|
||||
}
|
126
TINKLib/Model/Bikes/Bike/Bike.cs
Normal file
126
TINKLib/Model/Bikes/Bike/Bike.cs
Normal file
|
@ -0,0 +1,126 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TINK.Model.Bike
|
||||
{
|
||||
/// <summary> Count of wheels. </summary>
|
||||
public enum WheelType
|
||||
{
|
||||
Mono = 0,
|
||||
Two = 1,
|
||||
Trike = 2,
|
||||
}
|
||||
|
||||
/// <summary> Type of bike. </summary>
|
||||
public enum TypeOfBike
|
||||
{
|
||||
Allround = 0,
|
||||
Cargo = 1,
|
||||
Citybike = 2,
|
||||
}
|
||||
|
||||
public class Bike : IEquatable<Bike>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a bike.
|
||||
/// </summary>
|
||||
/// <param name="dateTimeProvider">Provider for current date time to calculate remainig time on demand for state of type reserved.</param>
|
||||
/// <param name="wheelType"></param>
|
||||
/// <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,
|
||||
WheelType? wheelType = null,
|
||||
TypeOfBike? typeOfBike = null,
|
||||
string description = null)
|
||||
{
|
||||
WheelType = wheelType;
|
||||
TypeOfBike = typeOfBike;
|
||||
Id = p_iId;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the unique id of the bike;
|
||||
/// </summary>
|
||||
public int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the count of wheels.
|
||||
/// </summary>
|
||||
public WheelType? WheelType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the type of bike.
|
||||
/// </summary>
|
||||
public TypeOfBike? TypeOfBike { get; }
|
||||
|
||||
/// <summary> Holds the description of the bike. </summary>
|
||||
public string Description { get; }
|
||||
|
||||
|
||||
/// <summary> Compares two bike object.</summary>
|
||||
/// <param name="obj">Object to compare with.</param>
|
||||
/// <returns>True if bikes are equal.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var l_oBike = obj as Bike;
|
||||
if (l_oBike == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals(l_oBike);
|
||||
}
|
||||
|
||||
/// <summary> Converts the instance to text.</summary>
|
||||
public new string ToString()
|
||||
{
|
||||
return WheelType == null || TypeOfBike == null
|
||||
? $"Id={Id}{(!string.IsNullOrEmpty(Description) ? $", {Description}" : "")}"
|
||||
: $"Id={Id}{(WheelType != null ? $", wheel(s)={WheelType}" : string.Empty)}{(TypeOfBike != null ? $"type={TypeOfBike}" : "")}.";
|
||||
}
|
||||
|
||||
/// <summary> Compares two bike object.</summary>
|
||||
/// <param name="obj">Object to compare with.</param>
|
||||
/// <returns>True if bikes are equal.</returns>
|
||||
public bool Equals(Bike other)
|
||||
{
|
||||
return other != null &&
|
||||
Id == other.Id &&
|
||||
WheelType == other.WheelType &&
|
||||
TypeOfBike == other.TypeOfBike
|
||||
&& Description == other.Description;
|
||||
}
|
||||
|
||||
/// <summary> Compares two bike object.</summary>
|
||||
/// <param name="obj">Object to compare with.</param>
|
||||
/// <returns>True if bikes are equal.</returns>
|
||||
public static bool operator ==(Bike bike1, Bike bike2)
|
||||
{
|
||||
return EqualityComparer<Bike>.Default.Equals(bike1, bike2);
|
||||
}
|
||||
|
||||
/// <summary> Compares two bike object.</summary>
|
||||
/// <param name="obj">Object to compare with.</param>
|
||||
/// <returns>True if bikes are equal.</returns>
|
||||
public static bool operator !=(Bike bike1, Bike bike2)
|
||||
{
|
||||
return !(bike1 == bike2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates hash code for bike object.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCode = -390870100;
|
||||
hashCode = hashCode * -1521134295 + Id.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + WheelType?.GetHashCode() ?? 0;
|
||||
hashCode = hashCode * -1521134295 + TypeOfBike?.GetHashCode() ?? 0;
|
||||
hashCode = hashCode * -1521134295 + Description?.GetHashCode() ?? 0;
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
}
|
151
TINKLib/Model/Bikes/Bike/BluetoothLock/BikeInfo.cs
Normal file
151
TINKLib/Model/Bikes/Bike/BluetoothLock/BikeInfo.cs
Normal file
|
@ -0,0 +1,151 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TINK.Model.Bikes.Bike;
|
||||
using TINK.Model.State;
|
||||
|
||||
namespace TINK.Model.Bike.BluetoothLock
|
||||
{
|
||||
public class BikeInfo : BC.BikeInfo, IBikeInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a bike info object for a available bike.
|
||||
/// </summary>
|
||||
/// <param name="bikeId">Unique id of bike.</param>
|
||||
/// <param name="lockId">Id of the lock.</param>
|
||||
/// <param name="lockGuid">GUID specifying the lock.</param>
|
||||
/// <param name="currentStationId">Id of station where bike is located.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="tariffDescription">Hold tariff description of bike.</param>
|
||||
/// <param name="wheelType">Trike, two wheels, mono, ....</param>
|
||||
public BikeInfo(
|
||||
int bikeId,
|
||||
int lockId,
|
||||
Guid lockGuid,
|
||||
int? currentStationId,
|
||||
Uri operatorUri = null,
|
||||
TariffDescription tariffDescription = null,
|
||||
bool? isDemo = DEFAULTVALUEISDEMO,
|
||||
IEnumerable<string> group = null,
|
||||
WheelType? wheelType = null,
|
||||
TypeOfBike? typeOfBike = null,
|
||||
string description = null) : base(
|
||||
new StateInfo(),
|
||||
bikeId,
|
||||
isDemo,
|
||||
group,
|
||||
wheelType,
|
||||
typeOfBike,
|
||||
description,
|
||||
currentStationId,
|
||||
operatorUri,
|
||||
tariffDescription)
|
||||
{
|
||||
LockInfo = new LockInfo.Builder { Id = lockId, Guid = lockGuid }.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a bike info object for a requested bike.
|
||||
/// </summary>
|
||||
/// <param name="dateTimeProvider">Provider for current date time to calculate remainig time on demand for state of type reserved.</param>
|
||||
/// <param name="id">Unique id of bike.</param>
|
||||
/// <param name="lockId">Id of the lock.</param>
|
||||
/// <param name="lockGuid">GUID specifying the lock.</param>
|
||||
/// <param name="requestedAt">Date time when bike was requested</param>
|
||||
/// <param name="mailAddress">Mail address of user which requested bike.</param>
|
||||
/// <param name="currentStationId">Name of station where bike is located, null if bike is on the road.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="tariffDescription">Hold tariff description of bike.</param>
|
||||
/// <param name="p_oDateTimeNowProvider">Date time provider to calculate reaining time.</param>
|
||||
/// <param name="wheelType"></param>
|
||||
public BikeInfo(
|
||||
int id,
|
||||
int lockId,
|
||||
Guid lockGuid,
|
||||
byte[] userKey,
|
||||
byte[] adminKey,
|
||||
byte[] seed,
|
||||
DateTime requestedAt,
|
||||
string mailAddress,
|
||||
int? currentStationId,
|
||||
Uri operatorUri,
|
||||
TariffDescription tariffDescription,
|
||||
Func<DateTime> dateTimeProvider,
|
||||
bool? isDemo = DEFAULTVALUEISDEMO,
|
||||
IEnumerable<string> group = null,
|
||||
WheelType? wheelType = null,
|
||||
TypeOfBike? typeOfBike = null,
|
||||
string description = null) : base(
|
||||
new StateInfo(
|
||||
dateTimeProvider,
|
||||
requestedAt,
|
||||
mailAddress,
|
||||
""),
|
||||
id,
|
||||
isDemo,
|
||||
group,
|
||||
wheelType,
|
||||
typeOfBike,
|
||||
description,
|
||||
currentStationId,
|
||||
operatorUri,
|
||||
tariffDescription)
|
||||
{
|
||||
LockInfo = new LockInfo.Builder { Id = lockId, Guid = lockGuid, UserKey = userKey, AdminKey = adminKey, Seed = seed }.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a bike info object for a booked bike.
|
||||
/// </summary>
|
||||
/// <param name="id">Unique id of bike.</param>
|
||||
/// <param name="lockId">Id of the lock.</param>
|
||||
/// <param name="lockGuid">GUID specifying the lock.</param>
|
||||
/// <param name="bookedAt">Date time when bike was booked</param>
|
||||
/// <param name="mailAddress">Mail address of user which booked bike.</param>
|
||||
/// <param name="currentStationId">Name of station where bike is located, null if bike is on the road.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="tariffDescription">Hold tariff description of bike.</param>
|
||||
/// <param name="wheelType"></param>
|
||||
public BikeInfo(
|
||||
int id,
|
||||
int lockId,
|
||||
Guid lockGuid,
|
||||
byte[] userKey,
|
||||
byte[] adminKey,
|
||||
byte[] seed,
|
||||
DateTime bookedAt,
|
||||
string mailAddress,
|
||||
int? currentStationId,
|
||||
Uri operatorUri,
|
||||
TariffDescription tariffDescription = null,
|
||||
bool? isDemo = DEFAULTVALUEISDEMO,
|
||||
IEnumerable<string> group = null,
|
||||
WheelType? wheelType = null,
|
||||
TypeOfBike? typeOfBike = null,
|
||||
string description = null) : base(
|
||||
new StateInfo(
|
||||
bookedAt,
|
||||
mailAddress,
|
||||
""),
|
||||
id,
|
||||
isDemo,
|
||||
group,
|
||||
wheelType,
|
||||
typeOfBike,
|
||||
description,
|
||||
currentStationId,
|
||||
operatorUri,
|
||||
tariffDescription)
|
||||
{
|
||||
LockInfo = new LockInfo.Builder { Id = lockId, Guid = lockGuid, UserKey = userKey, AdminKey = adminKey, Seed = seed }.Build();
|
||||
}
|
||||
|
||||
public BikeInfo(BC.BikeInfo bikeInfo, LockInfo lockInfo) : base(
|
||||
bikeInfo ?? throw new ArgumentException($"Can not copy-construct {typeof(BikeInfo).Name}-object. Source bike info must not be null."))
|
||||
{
|
||||
LockInfo = lockInfo
|
||||
?? throw new ArgumentException($"Can not copy-construct {typeof(BikeInfo).Name}-object. Source lock object must not be null.");
|
||||
}
|
||||
|
||||
public LockInfo LockInfo { get; private set; }
|
||||
}
|
||||
}
|
36
TINKLib/Model/Bikes/Bike/BluetoothLock/BikeInfoMutable.cs
Normal file
36
TINKLib/Model/Bikes/Bike/BluetoothLock/BikeInfoMutable.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using TINK.Model.Bikes.Bike;
|
||||
using TINK.Model.Bikes.Bike.BluetoothLock;
|
||||
|
||||
namespace TINK.Model.Bike.BluetoothLock
|
||||
{
|
||||
public class BikeInfoMutable : BC.BikeInfoMutable, IBikeInfoMutable
|
||||
{
|
||||
/// <summary> Constructs a bike object from source. </summary>
|
||||
public BikeInfoMutable(BikeInfo bike) : base(
|
||||
bike.Id,
|
||||
bike.IsDemo,
|
||||
bike.Group,
|
||||
bike.WheelType,
|
||||
bike.TypeOfBike,
|
||||
bike.Description,
|
||||
bike.CurrentStation,
|
||||
bike.OperatorUri,
|
||||
bike.TariffDescription,
|
||||
() => DateTime.Now,
|
||||
bike.State)
|
||||
{
|
||||
LockInfo = new LockInfoMutable(
|
||||
bike.LockInfo.Id,
|
||||
bike.LockInfo.Guid,
|
||||
bike.LockInfo.UserKey,
|
||||
bike.LockInfo.AdminKey,
|
||||
bike.LockInfo.Seed,
|
||||
bike.LockInfo.State);
|
||||
}
|
||||
|
||||
public LockInfoMutable LockInfo { get; }
|
||||
|
||||
ILockInfoMutable IBikeInfoMutable.LockInfo => LockInfo;
|
||||
}
|
||||
}
|
6
TINKLib/Model/Bikes/Bike/BluetoothLock/IBikeInfo.cs
Normal file
6
TINKLib/Model/Bikes/Bike/BluetoothLock/IBikeInfo.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace TINK.Model.Bike.BluetoothLock
|
||||
{
|
||||
public interface IBikeInfo : BC.IBikeInfo
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace TINK.Model.Bikes.Bike.BluetoothLock
|
||||
{
|
||||
public interface IBikeInfoMutable : BC.IBikeInfoMutable
|
||||
{
|
||||
ILockInfoMutable LockInfo { get; }
|
||||
}
|
||||
}
|
24
TINKLib/Model/Bikes/Bike/BluetoothLock/ILockInfoMutable.cs
Normal file
24
TINKLib/Model/Bikes/Bike/BluetoothLock/ILockInfoMutable.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using TINK.Model.Bike.BluetoothLock;
|
||||
|
||||
namespace TINK.Model.Bikes.Bike.BluetoothLock
|
||||
{
|
||||
public interface ILockInfoMutable
|
||||
{
|
||||
/// <summary> Identification number of bluetooth lock.</summary>
|
||||
int Id { get; }
|
||||
|
||||
/// <summary> Gets the user key.</summary>
|
||||
byte[] UserKey { get; }
|
||||
|
||||
LockingState State { get; set; }
|
||||
|
||||
/// <summary> Holds the percentage of lock battery.</summary>
|
||||
double BatteryPercentage { get; set; }
|
||||
|
||||
/// <summary> Changes during runtime: Can be unknown when set from copri and chang to a valid value when set from lock.</summary>
|
||||
Guid Guid { get; set; }
|
||||
|
||||
byte[] Seed { get; }
|
||||
}
|
||||
}
|
54
TINKLib/Model/Bikes/Bike/BluetoothLock/LockInfoMutable.cs
Normal file
54
TINKLib/Model/Bikes/Bike/BluetoothLock/LockInfoMutable.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using TINK.Model.Bikes.Bike.BluetoothLock;
|
||||
|
||||
namespace TINK.Model.Bike.BluetoothLock
|
||||
{
|
||||
public class LockInfoMutable : ILockInfoMutable
|
||||
{
|
||||
/// <summary> Lock info object. </summary>
|
||||
private LockInfo LockInfo { get; set; }
|
||||
|
||||
/// <summary> Constructs a bluetooth lock info object. </summary>
|
||||
/// <param name="id">Id of lock must always been known when constructing an lock info object.</param>
|
||||
public LockInfoMutable(
|
||||
int id,
|
||||
Guid guid,
|
||||
byte[] userKey,
|
||||
byte[] adminKey,
|
||||
byte[] seed,
|
||||
LockingState state)
|
||||
{
|
||||
LockInfo = new LockInfo.Builder() { Id = id, Guid = guid, UserKey = userKey, AdminKey = adminKey, Seed = seed, State = state }.Build();
|
||||
}
|
||||
|
||||
public int Id => LockInfo.Id;
|
||||
|
||||
/// <summary> Changes during runtime: Can be unknown when set from copri and chang to a valid value when set from lock.</summary>
|
||||
public Guid Guid
|
||||
{
|
||||
get => LockInfo.Guid;
|
||||
set => LockInfo = new LockInfo.Builder(LockInfo) { Guid = value }.Build();
|
||||
}
|
||||
|
||||
public byte[] Seed => LockInfo.Seed;
|
||||
|
||||
public byte[] UserKey => LockInfo.UserKey;
|
||||
|
||||
public byte[] AdminKey => LockInfo.AdminKey;
|
||||
|
||||
public LockingState State
|
||||
{
|
||||
get => LockInfo.State;
|
||||
set => LockInfo = new LockInfo.Builder(LockInfo) { State = value }.Build();
|
||||
}
|
||||
|
||||
/// <summary> Holds the percentage of lock battery.</summary>
|
||||
public double BatteryPercentage { get; set; } = double.NaN;
|
||||
|
||||
/// <summary> Loads lock info object from values. </summary>
|
||||
public void Load(int id, Guid guid, byte[] seed, byte[] userKey, byte[] adminKey)
|
||||
{
|
||||
LockInfo = new LockInfo.Builder(LockInfo) { Id = id, Guid = guid, Seed = seed, UserKey = userKey, AdminKey = adminKey}.Build();
|
||||
}
|
||||
}
|
||||
}
|
40
TINKLib/Model/Bikes/Bike/TariffDescription.cs
Normal file
40
TINKLib/Model/Bikes/Bike/TariffDescription.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
|
||||
namespace TINK.Model.Bikes.Bike
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds tariff info for a single bike.
|
||||
/// </summary>
|
||||
public record TariffDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the tariff.
|
||||
/// </summary>
|
||||
public string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of the tariff.
|
||||
/// </summary>
|
||||
public int? Number { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Costs per hour in euro.
|
||||
/// </summary>
|
||||
public double FeeEuroPerHour { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Costs of the abo per month.
|
||||
/// </summary>
|
||||
public double AboEuroPerMonth { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Costs per hour in euro.
|
||||
/// </summary>
|
||||
public TimeSpan FreeTimePerSession { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Max. costs per day in euro.
|
||||
/// </summary>
|
||||
public double MaxFeeEuroPerDay { get; init; }
|
||||
}
|
||||
}
|
54
TINKLib/Model/Bikes/BikeCollection.cs
Normal file
54
TINKLib/Model/Bikes/BikeCollection.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.Linq;
|
||||
|
||||
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
|
||||
|
||||
namespace TINK.Model.Bike
|
||||
{
|
||||
public class BikeCollection : IBikeDictionary<BikeInfo>
|
||||
{
|
||||
/// <summary> Holds the bike dictionary object.</summary>
|
||||
private Dictionary<int, BikeInfo> BikeDictionary { get; }
|
||||
|
||||
/// <summary>Constructs an empty bike info dictionary object.</summary>
|
||||
public BikeCollection()
|
||||
{
|
||||
BikeDictionary = new Dictionary<int, BikeInfo>();
|
||||
}
|
||||
|
||||
/// <summary> Constructs a bike collection object.</summary>
|
||||
/// <param name="bikeDictionary"></param>
|
||||
public BikeCollection(Dictionary<int, 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>
|
||||
/// <returns></returns>
|
||||
public BikeInfo GetById(int p_iId)
|
||||
{
|
||||
return BikeDictionary.FirstOrDefault(x => x.Key == p_iId).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>
|
||||
/// <returns>True if bike is contained, false otherwise.</returns>
|
||||
public bool ContainsKey(int p_iId) => BikeDictionary.Keys.Contains(p_iId);
|
||||
|
||||
/// <summary> Gets the enumerator. </summary>
|
||||
/// <returns>Enumerator object.</returns>
|
||||
public IEnumerator<BikeInfo> GetEnumerator() => BikeDictionary.Values.GetEnumerator();
|
||||
|
||||
/// <summary> Gets the enumerator. </summary>
|
||||
/// <returns>Enumerator object.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
34
TINKLib/Model/Bikes/BikeCollectionFilter.cs
Normal file
34
TINKLib/Model/Bikes/BikeCollectionFilter.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TINK.Model.Bike;
|
||||
|
||||
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
|
||||
|
||||
namespace TINK.Model
|
||||
{
|
||||
public static class BikeCollectionFilter
|
||||
{
|
||||
/// <summary> Filters bikes by station. </summary>
|
||||
/// <param name="bikesAtAnyStation">Bikes available, requested and/ or occupied bikes to filter.</param>
|
||||
/// <param name="selectedStation">Id of station, might be null</param>
|
||||
/// <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)
|
||||
{
|
||||
return new BikeCollection(bikesAtAnyStation?
|
||||
.Where(bike => selectedStation.HasValue && bike.CurrentStation == selectedStation.Value)
|
||||
.ToDictionary(x => x.Id) ?? new Dictionary<int, BikeInfo>());
|
||||
}
|
||||
|
||||
/// <summary> Filters bikes by bike type. </summary>
|
||||
/// <param name="bcAndLockItBikes">Bikes available, requested and/ or occupied bikes to filter.</param>
|
||||
/// <returns>BikeCollection holding LockIt-bikes empty BikeCollection, if there are no LockIt-bikes.</returns>
|
||||
public static BikeCollection GetLockIt(this BikeCollection bcAndLockItBikes)
|
||||
{
|
||||
return new BikeCollection(bcAndLockItBikes?
|
||||
.Where(bike => bike is Bike.BluetoothLock.BikeInfo)
|
||||
.ToDictionary(x => x.Id) ?? new Dictionary<int, BikeInfo>());
|
||||
}
|
||||
}
|
||||
}
|
145
TINKLib/Model/Bikes/BikeCollectionMutable.cs
Normal file
145
TINKLib/Model/Bikes/BikeCollectionMutable.cs
Normal file
|
@ -0,0 +1,145 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
|
||||
using BikeInfoMutable = TINK.Model.Bike.BC.BikeInfoMutable;
|
||||
|
||||
namespace TINK.Model.Bike
|
||||
{
|
||||
/// <summary> Holds entity of bikes. </summary>
|
||||
public class BikeCollectionMutable : ObservableCollection<BikeInfoMutable>, IBikeDictionaryMutable<BikeInfoMutable>
|
||||
{
|
||||
/// <summary> Constructs a mutable bike collection object. </summary>
|
||||
public BikeCollectionMutable()
|
||||
{
|
||||
SelectedBike = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates bikes dictionary from bikes response, i.e.
|
||||
/// - removes bikes which are no more contained in bikes response
|
||||
/// - updates state of all bikes from state contained in bikes response
|
||||
/// </summary>
|
||||
/// <param name="bikesAll"> Object holding bikes info from copri to update from.</param>
|
||||
/// <param name="p_oDateTimeProvider">Provices date time information.</param>
|
||||
public void Update(
|
||||
IEnumerable<BikeInfo> bikesAll)
|
||||
{
|
||||
// Get list of current bikes by state(s) to update.
|
||||
// Needed to remove bikes which switched state and have to be removed from collection.
|
||||
var bikesToBeRemoved = this.Select(x => x.Id).ToList();
|
||||
|
||||
foreach (var bikeInfo in (bikesAll ?? new List<BikeInfo>()))
|
||||
{
|
||||
/// Check if bike has to be added to list of existing station.
|
||||
if (ContainsKey(bikeInfo.Id) == false)
|
||||
{
|
||||
// Bike does not yet exist in list of bikes.
|
||||
Add(BikeInfoMutableFactory.Create(bikeInfo));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update bike.
|
||||
GetById(bikeInfo.Id).State.Load(bikeInfo.State);
|
||||
|
||||
if (bikesToBeRemoved.Contains<int>(bikeInfo.Id))
|
||||
{
|
||||
// Remove list from obsolete list.
|
||||
bikesToBeRemoved.Remove(bikeInfo.Id);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove obsolete bikes.
|
||||
foreach (var l_oId in bikesToBeRemoved)
|
||||
{
|
||||
RemoveById(l_oId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new bike to collecion of bike.
|
||||
/// </summary>
|
||||
/// <param name="p_oNewBike">New bike to add.</param>
|
||||
/// <exception cref="Exception">Thrown if bike is not unique.</exception>
|
||||
public new void Add(BikeInfoMutable p_oNewBike)
|
||||
{
|
||||
// Ensure that bike id of new bike is is unique
|
||||
foreach (BikeInfoMutable l_oBike in Items)
|
||||
{
|
||||
if (l_oBike.Id == p_oNewBike.Id)
|
||||
{
|
||||
throw new Exception(string.Format("Can not add bike with {0} to collection ob bike. Id is not unnique.", p_oNewBike));
|
||||
}
|
||||
}
|
||||
|
||||
base.Add(p_oNewBike);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bike selected by user for regerving or cancel reservation.
|
||||
/// </summary>
|
||||
public BikeInfoMutable SelectedBike
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public void SetSelectedBike(int p_intId)
|
||||
{
|
||||
SelectedBike = GetById(p_intId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a bike by its id.
|
||||
/// </summary>
|
||||
/// <param name="p_iId"></param>
|
||||
/// <returns></returns>
|
||||
public BikeInfoMutable GetById(int p_iId)
|
||||
{
|
||||
{
|
||||
return this.FirstOrDefault(bike => bike.Id == p_iId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deteermines whether a bike by given key exists.
|
||||
/// </summary>
|
||||
/// <param name="p_strKey">Key to check.</param>
|
||||
/// <returns>True if bike exists.</returns>
|
||||
public bool ContainsKey(int p_iId)
|
||||
{
|
||||
return GetById(p_iId) != 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)
|
||||
{
|
||||
var l_oBike = GetById(p_iId);
|
||||
if (l_oBike == null)
|
||||
{
|
||||
// Nothing to do if bike does not exists.
|
||||
return;
|
||||
}
|
||||
|
||||
Remove(l_oBike);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create mutable objects from immutable objects.
|
||||
/// </summary>
|
||||
private static class BikeInfoMutableFactory
|
||||
{
|
||||
public static BikeInfoMutable Create(BikeInfo bikeInfo)
|
||||
{
|
||||
return (bikeInfo is BluetoothLock.BikeInfo bluetoothLockBikeInfo)
|
||||
? new BluetoothLock.BikeInfoMutable(bluetoothLockBikeInfo)
|
||||
: new BikeInfoMutable(bikeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
38
TINKLib/Model/Bikes/BikeCollectionUpdater.cs
Normal file
38
TINKLib/Model/Bikes/BikeCollectionUpdater.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TINK.Model.Bike
|
||||
{
|
||||
public static class BikeCollectionUpdater
|
||||
{
|
||||
/// <summary> Updates bikes lock info with the latest lock info from bluetooth service.</summary>
|
||||
/// <param name="bikes">bikes to be updated.</param>
|
||||
/// <param name="locksInfo">locks info to be used for updating bikes.</param>
|
||||
/// <returns></returns>
|
||||
public static BikeCollection UpdateLockInfo(
|
||||
this BikeCollection bikes,
|
||||
IEnumerable<BluetoothLock.LockInfo> locksInfo)
|
||||
{
|
||||
|
||||
var updatedBikesCollection = new Dictionary<int, BC.BikeInfo>();
|
||||
|
||||
foreach (var bikeInfo in bikes)
|
||||
{
|
||||
if (!(bikeInfo is BluetoothLock.BikeInfo bluetoothBikeInfo))
|
||||
{
|
||||
// No processing needed because bike is not a bluetooth bike
|
||||
updatedBikesCollection.Add(bikeInfo.Id, bikeInfo);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if to update current bike lock info using state from bluetooth service or not.
|
||||
var currentLockInfo = locksInfo.FirstOrDefault(x => x.Id == bluetoothBikeInfo.LockInfo.Id) // Update bike info with latest info from bluethooth service if available
|
||||
?? bluetoothBikeInfo.LockInfo; // Use lock info state object from copri which holds a lock id and a state of value unknown.
|
||||
|
||||
updatedBikesCollection.Add(bluetoothBikeInfo.Id, new BluetoothLock.BikeInfo(bluetoothBikeInfo, currentLockInfo));
|
||||
}
|
||||
|
||||
return new BikeCollection(updatedBikesCollection);
|
||||
}
|
||||
}
|
||||
}
|
35
TINKLib/Model/Bikes/IBikeCollection.cs
Normal file
35
TINKLib/Model/Bikes/IBikeCollection.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace TINK.Model.Bike
|
||||
{
|
||||
public interface IBikeDictionary<T> : IReadOnlyCollection<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a bike by its id.
|
||||
/// </summary>
|
||||
/// <param name="p_iId"></param>
|
||||
/// <returns></returns>
|
||||
T GetById(int p_iId);
|
||||
|
||||
/// <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);
|
||||
}
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new element to dictinary.
|
||||
/// </summary>
|
||||
/// <param name="p_oNewElement">New element to add.</param>
|
||||
void Add(T p_oNewElement);
|
||||
}
|
||||
}
|
141
TINKLib/Model/Connector/Command/Command.cs
Normal file
141
TINKLib/Model/Connector/Command/Command.cs
Normal file
|
@ -0,0 +1,141 @@
|
|||
using Serilog;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Repository;
|
||||
using TINK.Model.Repository.Request;
|
||||
using TINK.Model.Repository.Response;
|
||||
using TINK.Model.User.Account;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
public class Command : Base, ICommand
|
||||
{
|
||||
/// <summary> True if connector has access to copri server, false if cached values are used. </summary>
|
||||
public bool IsConnected => CopriServer.IsConnected;
|
||||
|
||||
/// <summary> No user is logged in.</summary>
|
||||
public string SessionCookie => null;
|
||||
|
||||
/// <summary> Is raised whenever login state has changed.</summary>
|
||||
public event LoginStateChangedEventHandler LoginStateChanged;
|
||||
|
||||
/// <summary>Constructs a copri query object.</summary>
|
||||
/// <param name="p_oCopriServer">Server which implements communication.</param>
|
||||
public Command(
|
||||
ICopriServerBase p_oCopriServer) : base(p_oCopriServer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 exception is thrown.
|
||||
/// </summary>
|
||||
public async Task<IAccount> DoLogin(
|
||||
string p_strMail,
|
||||
string p_strPassword,
|
||||
string p_strDeviceId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(p_strMail))
|
||||
{
|
||||
throw new ArgumentNullException("Can not loging user. Mail address must not be null or empty.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(p_strPassword))
|
||||
{
|
||||
throw new ArgumentNullException("Can not loging user. Password must not be null or empty.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(p_strDeviceId))
|
||||
{
|
||||
throw new ArgumentNullException("Can not loging user. Device not be null or empty.");
|
||||
}
|
||||
|
||||
AuthorizationResponse l_oResponse;
|
||||
try
|
||||
{
|
||||
l_oResponse = (await CopriServer.DoAuthorizationAsync(p_strMail, p_strPassword, p_strDeviceId)).GetIsResponseOk(p_strMail);
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
var l_oAccount = l_oResponse.GetAccount(MerchantId, p_strMail, p_strPassword);
|
||||
|
||||
// Log in state changes. Notify parent object to update.
|
||||
LoginStateChanged?.Invoke(this, new LoginStateChangedEventArgs(l_oAccount.SessionCookie, l_oAccount.Mail));
|
||||
|
||||
return l_oAccount;
|
||||
}
|
||||
|
||||
/// <summary> Logs user out. </summary>
|
||||
public async Task DoLogout()
|
||||
{
|
||||
Log.ForContext<Command>().Error("Unexpected log out request detected. No user logged in.");
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to reserve a bike.
|
||||
/// </summary>
|
||||
/// <param name="p_oBike">Bike to book.</param>
|
||||
public async Task DoReserve(
|
||||
Bikes.Bike.BC.IBikeInfoMutable p_oBike)
|
||||
{
|
||||
Log.ForContext<Command>().Error("Unexpected booking request detected. No user logged in.");
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary> Request to cancel a reservation.</summary>
|
||||
/// <param name="p_oBike">Bike to book.</param>
|
||||
public async Task DoCancelReservation(Bikes.Bike.BC.IBikeInfoMutable bike)
|
||||
{
|
||||
Log.ForContext<Command>().Error("Unexpected cancel reservation request detected. No user logged in.");
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary> Get authentication keys.</summary>
|
||||
/// <param name="bike">Bike to book.</param>
|
||||
public async Task CalculateAuthKeys(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike)
|
||||
{
|
||||
Log.ForContext<Command>().Error("Unexpected request to get authenticatin keys detected. No user logged in.");
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary> Updates COPRI lock state for a booked bike. </summary>
|
||||
/// <param name="bike">Bike to update locking state for.</param>
|
||||
/// <param name="location">Location where lock was opened/ changed.</param>
|
||||
/// <returns>Response on updating locking state.</returns>
|
||||
public async Task UpdateLockingStateAsync(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike, LocationDto location)
|
||||
{
|
||||
Log.ForContext<Command>().Error("Unexpected request to update locking state detected. No user logged in.");
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DoBook(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike)
|
||||
{
|
||||
Log.ForContext<Command>().Error("Unexpected booking request detected. No user logged in.");
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DoReturn(
|
||||
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike,
|
||||
LocationDto location)
|
||||
{
|
||||
Log.ForContext<Command>().Error("Unexpected returning request detected. No user logged in.");
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submits feedback to copri server.
|
||||
/// </summary>
|
||||
/// <param name="userFeedback">Feedback to submit.</param>
|
||||
public async Task DoSubmitFeedback(ICommand.IUserFeedback userFeedback, Uri opertorUri)
|
||||
{
|
||||
Log.ForContext<Command>().Error("Unexpected submit feedback request detected. No user logged in.");
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
279
TINKLib/Model/Connector/Command/CommandLoggedIn.cs
Normal file
279
TINKLib/Model/Connector/Command/CommandLoggedIn.cs
Normal file
|
@ -0,0 +1,279 @@
|
|||
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.Model.User.Account;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
public class CommandLoggedIn : BaseLoggedIn, ICommand
|
||||
{
|
||||
/// <summary> True if connector has access to copri server, false if cached values are used. </summary>
|
||||
public bool IsConnected => CopriServer.IsConnected;
|
||||
|
||||
/// <summary> Is raised whenever login state has changed.</summary>
|
||||
public event LoginStateChangedEventHandler LoginStateChanged;
|
||||
|
||||
/// <summary>Constructs a copri query object.</summary>
|
||||
/// <param name="p_oCopriServer">Server which implements communication.</param>
|
||||
public CommandLoggedIn(ICopriServerBase p_oCopriServer,
|
||||
string p_strSessionCookie,
|
||||
string p_strMail,
|
||||
Func<DateTime> p_oDateTimeProvider) : base(p_oCopriServer, p_strSessionCookie, p_strMail, p_oDateTimeProvider)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="p_oAccount">Account to use for login.</param>
|
||||
public Task<IAccount> DoLogin(string p_strMail, string p_strPassword, string p_strDeviceId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(p_strMail))
|
||||
{
|
||||
throw new ArgumentNullException("Can not loging user. Mail address must not be null or empty.");
|
||||
}
|
||||
|
||||
throw new Exception($"Fehler beim Anmelden von unter {p_strMail}. Benutzer {Mail} ist bereits angemeldet.");
|
||||
}
|
||||
|
||||
/// <summary> Logs user out. </summary>
|
||||
public async Task DoLogout()
|
||||
{
|
||||
AuthorizationoutResponse l_oResponse = null;
|
||||
try
|
||||
{
|
||||
l_oResponse = (await CopriServer.DoAuthoutAsync()).GetIsResponseOk();
|
||||
}
|
||||
catch (AuthcookieNotDefinedException)
|
||||
{
|
||||
// Cookie is no more defined, i.e. no need to logout user at copri because user is already logged out.
|
||||
// Just ignore this error.
|
||||
// User logged out, log in state changed. Notify parent object to update.
|
||||
LoginStateChanged?.Invoke(this, new LoginStateChangedEventArgs());
|
||||
return;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
// User logged out, log in state changed. Notify parent object to update.
|
||||
LoginStateChanged?.Invoke(this, new LoginStateChangedEventArgs());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to reserve a bike.
|
||||
/// </summary>
|
||||
/// <param name="bike">Bike to book.</param>
|
||||
public async Task DoReserve(Bikes.Bike.BC.IBikeInfoMutable bike)
|
||||
{
|
||||
if (bike == null)
|
||||
{
|
||||
throw new ArgumentNullException("Can not reserve bike. No bike object available.");
|
||||
}
|
||||
|
||||
BikeInfoReservedOrBooked response;
|
||||
try
|
||||
{
|
||||
response = (await CopriServer.DoReserveAsync(bike.Id, bike.OperatorUri)).GetIsReserveResponseOk(bike.Id);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Exception was not expected or too many subsequent excepitons detected.
|
||||
throw;
|
||||
}
|
||||
|
||||
bike.Load(response, Mail, DateTimeProvider, Bikes.Bike.BC.NotifyPropertyChangedLevel.None);
|
||||
}
|
||||
|
||||
/// <summary> Request to cancel a reservation.</summary>
|
||||
/// <param name="bike">Bike to cancel reservation.</param>
|
||||
public async Task DoCancelReservation(
|
||||
Bikes.Bike.BC.IBikeInfoMutable bike)
|
||||
{
|
||||
if (bike == null)
|
||||
{
|
||||
throw new ArgumentNullException("Can not cancel reservation of bike. No bike object available.");
|
||||
}
|
||||
|
||||
ReservationCancelReturnResponse response;
|
||||
try
|
||||
{
|
||||
response = (await CopriServer.DoCancelReservationAsync(bike.Id, bike.OperatorUri)).GetIsCancelReservationResponseOk(bike.Id);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Exception was not expected or too many subsequent excepitons detected.
|
||||
throw;
|
||||
}
|
||||
|
||||
bike.Load(Bikes.Bike.BC.NotifyPropertyChangedLevel.None);
|
||||
}
|
||||
|
||||
/// <summary> Get authentication keys.</summary>
|
||||
/// <param name="bike">Bike to get new keys for.</param>
|
||||
public async Task CalculateAuthKeys(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike)
|
||||
{
|
||||
if (bike == null)
|
||||
{
|
||||
throw new ArgumentNullException("Can not calculate auth keys. No bike object available.");
|
||||
}
|
||||
|
||||
switch (bike.State.Value)
|
||||
{
|
||||
case State.InUseStateEnum.Reserved:
|
||||
case State.InUseStateEnum.Booked:
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentNullException($"Can not calculate auth keys. Unexpected bike state {bike.State.Value} detected.");
|
||||
}
|
||||
|
||||
BikeInfoReservedOrBooked response;
|
||||
Guid guid = (bike is BikeInfoMutable btBike) ? btBike.LockInfo.Guid : new Guid();
|
||||
try
|
||||
{
|
||||
response = (await CopriServer.CalculateAuthKeysAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Exception was not expected or too many subsequent excepitons detected.
|
||||
throw;
|
||||
}
|
||||
|
||||
UpdaterJSON.Load(
|
||||
bike,
|
||||
response,
|
||||
Mail,
|
||||
DateTimeProvider,
|
||||
Bikes.Bike.BC.NotifyPropertyChangedLevel.None);
|
||||
}
|
||||
|
||||
/// <summary> Updates COPRI lock state for a booked bike. </summary>
|
||||
/// <param name="bike">Bike to update locking state for.</param>
|
||||
/// <returns>Response on updating locking state.</returns>
|
||||
public async Task UpdateLockingStateAsync(
|
||||
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike, LocationDto location)
|
||||
{
|
||||
if (bike == null)
|
||||
{
|
||||
throw new ArgumentNullException("Can not book bike. No bike object available.");
|
||||
}
|
||||
|
||||
if (bike.State.Value != State.InUseStateEnum.Booked)
|
||||
{
|
||||
throw new ArgumentNullException($"Can not update locking state of bike. Unexpected booking state {bike.State} detected.");
|
||||
}
|
||||
|
||||
lock_state? state = null;
|
||||
switch (bike.LockInfo.State)
|
||||
{
|
||||
case LockingState.Open:
|
||||
state = lock_state.unlocked;
|
||||
break;
|
||||
|
||||
case LockingState.Closed:
|
||||
state = lock_state.locked;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!state.HasValue)
|
||||
{
|
||||
throw new ArgumentNullException($"Can not update locking state of bike. Unexpected locking state {bike.LockInfo.State} detected.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
(await CopriServer.UpdateLockingStateAsync(
|
||||
bike.Id,
|
||||
location,
|
||||
state.Value,
|
||||
bike.LockInfo.BatteryPercentage,
|
||||
bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Exception was not expected or too many subsequent excepitons detected.
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary> Request to book a bike. </summary>
|
||||
/// <param name="bike">Bike to book.</param>
|
||||
public async Task DoBook(
|
||||
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike)
|
||||
{
|
||||
if (bike == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bike), "Can not book bike. No bike object available.");
|
||||
}
|
||||
|
||||
BikeInfoReservedOrBooked response;
|
||||
var btBike = bike as BikeInfoMutable;
|
||||
Guid guid = btBike != null ? btBike.LockInfo.Guid : new Guid();
|
||||
double batteryPercentage = btBike != null ? btBike.LockInfo.BatteryPercentage : double.NaN;
|
||||
try
|
||||
{
|
||||
response = (await CopriServer.DoBookAsync(
|
||||
bike.Id,
|
||||
guid,
|
||||
batteryPercentage,
|
||||
bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Exception was not expected or too many subsequent excepitons detected.
|
||||
throw;
|
||||
}
|
||||
|
||||
bike.Load(
|
||||
response,
|
||||
Mail,
|
||||
DateTimeProvider,
|
||||
Bikes.Bike.BC.NotifyPropertyChangedLevel.None);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public async Task DoReturn(
|
||||
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike,
|
||||
LocationDto location)
|
||||
{
|
||||
if (bike == null)
|
||||
{
|
||||
throw new ArgumentNullException("Can not return bike. No bike object available.");
|
||||
}
|
||||
|
||||
ReservationCancelReturnResponse l_oResponse;
|
||||
try
|
||||
{
|
||||
l_oResponse = (await CopriServer.DoReturn(bike.Id, location, bike.OperatorUri)).GetIsReturnBikeResponseOk(bike.Id);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Exception was not expected or too many subsequent exceptions detected.
|
||||
throw;
|
||||
}
|
||||
|
||||
bike.Load(Bikes.Bike.BC.NotifyPropertyChangedLevel.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submits feedback to copri server.
|
||||
/// </summary>
|
||||
/// <param name="userFeedback">Feedback to submit.</param>
|
||||
public async Task DoSubmitFeedback(ICommand.IUserFeedback userFeedback, Uri opertorUri)
|
||||
=> await CopriServer.DoSubmitFeedback(userFeedback.Message, userFeedback.IsBikeBroken, opertorUri);
|
||||
|
||||
}
|
||||
}
|
99
TINKLib/Model/Connector/Command/ICommand.cs
Normal file
99
TINKLib/Model/Connector/Command/ICommand.cs
Normal file
|
@ -0,0 +1,99 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Repository.Request;
|
||||
using TINK.Model.User.Account;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
public interface ICommand
|
||||
{
|
||||
/// <summary> Is raised whenever login state has changed.</summary>
|
||||
event LoginStateChangedEventHandler LoginStateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 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 exception is thrown.
|
||||
/// </summary>
|
||||
Task<IAccount> DoLogin(string p_strMail, string p_strPassword, string p_strDeviceId);
|
||||
|
||||
/// <summary> Logs user out. </summary>
|
||||
Task DoLogout();
|
||||
|
||||
/// <summary> Request to reserve a bike.</summary>
|
||||
/// <param name="p_oBike">Bike to book.</param>
|
||||
Task DoReserve(Bikes.Bike.BC.IBikeInfoMutable p_oBike);
|
||||
|
||||
/// <summary> Request to cancel a reservation.</summary>
|
||||
/// <param name="p_oBike">Bike to book.</param>
|
||||
Task DoCancelReservation(Bikes.Bike.BC.IBikeInfoMutable p_oBike);
|
||||
|
||||
/// <summary> Get authentication keys to connect to lock.</summary>
|
||||
/// <param name="bike">Bike to book.</param>
|
||||
Task CalculateAuthKeys(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike);
|
||||
|
||||
/// <summary> Updates COPRI lock state for a booked bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to update locking state for.</param>
|
||||
/// <param name="location">Geolocation of lock when returning bike.</param>
|
||||
/// <returns>Response on updating locking state.</returns>
|
||||
Task UpdateLockingStateAsync(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike, LocationDto location = null);
|
||||
|
||||
/// <summary> Request to book a bike.</summary>
|
||||
/// <param name="bike">Bike to book.</param>
|
||||
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);
|
||||
|
||||
/// <summary> True if connector has access to copri server, false if cached values are used. </summary>
|
||||
bool IsConnected { get; }
|
||||
|
||||
/// <summary> True if user is logged in false if not. </summary>
|
||||
string SessionCookie { get; }
|
||||
|
||||
Task DoSubmitFeedback(IUserFeedback userFeedback, Uri opertorUri);
|
||||
|
||||
/// <summary>
|
||||
/// Feedback given by user when returning bike.
|
||||
/// </summary>
|
||||
public interface IUserFeedback
|
||||
{
|
||||
/// <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; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
|
||||
/// <summary> Event arguments to notify about changes of logged in state.</summary>
|
||||
public class LoginStateChangedEventArgs : EventArgs
|
||||
{
|
||||
public LoginStateChangedEventArgs() : this(string.Empty, string.Empty)
|
||||
{ }
|
||||
|
||||
public LoginStateChangedEventArgs(string p_strSessionCookie, string p_strMail)
|
||||
{
|
||||
SessionCookie = p_strSessionCookie;
|
||||
Mail = p_strMail;
|
||||
}
|
||||
|
||||
public string SessionCookie { get; }
|
||||
|
||||
public string Mail { get; }
|
||||
}
|
||||
}
|
9
TINKLib/Model/Connector/Command/UserFeedbackDto.cs
Normal file
9
TINKLib/Model/Connector/Command/UserFeedbackDto.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
public record UserFeedbackDto : ICommand.IUserFeedback
|
||||
{
|
||||
public bool IsBikeBroken { get; init; }
|
||||
public string Message { get; init; }
|
||||
}
|
||||
}
|
57
TINKLib/Model/Connector/Connector.cs
Normal file
57
TINKLib/Model/Connector/Connector.cs
Normal file
|
@ -0,0 +1,57 @@
|
|||
using System;
|
||||
using TINK.Model.Services.CopriApi;
|
||||
using TINK.Model.Repository;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
/// <summary>
|
||||
/// Connects tink app to copri by getting data from copri and updating tink app model (i.e. bikes, user, ...)
|
||||
/// </summary>
|
||||
public class Connector : IConnector
|
||||
{
|
||||
/// <summary>Constructs a copri connector object.</summary>
|
||||
/// <param name="activeUri"> Uri to connect to.</param>
|
||||
/// <param name="userAgent">Holds the name and version of the TINKApp.</param>
|
||||
/// /// <param name="sessionCookie"> Holds the session cookie.</param>
|
||||
/// <param name="p_strMail">Mail of user.</param>
|
||||
/// <param name="expiresAfter">Timespan which holds value after which cache expires.</param>
|
||||
/// <param name="server"> Provides cached addess to copri.</param>
|
||||
public Connector(
|
||||
Uri activeUri,
|
||||
string userAgent,
|
||||
string sessionCookie,
|
||||
string mail,
|
||||
TimeSpan? expiresAfter = null,
|
||||
ICachedCopriServer server = null )
|
||||
{
|
||||
Command = GetCommand(
|
||||
server ?? new CopriProviderHttps(activeUri, TinkApp.MerchantId, userAgent, sessionCookie),
|
||||
sessionCookie,
|
||||
mail);
|
||||
|
||||
Query = GetQuery(
|
||||
server ?? new CopriProviderHttps(activeUri, TinkApp.MerchantId, userAgent, sessionCookie, expiresAfter),
|
||||
sessionCookie,
|
||||
mail);
|
||||
}
|
||||
|
||||
/// <summary> Object for queriying stations and bikes.</summary>
|
||||
public ICommand Command { get; private set; }
|
||||
|
||||
/// <summary> Object for queriying stations and bikes.</summary>
|
||||
public IQuery Query { get; private set; }
|
||||
|
||||
/// <summary> True if connector has access to copri server, false if cached values are used. </summary>
|
||||
public bool IsConnected => Command.IsConnected;
|
||||
|
||||
/// <summary> Gets a command object to perform copri commands. </summary>
|
||||
public static ICommand GetCommand(ICopriServerBase copri, string sessioncookie, string mail) => string.IsNullOrEmpty(sessioncookie)
|
||||
? new Command(copri)
|
||||
: new CommandLoggedIn(copri, sessioncookie, mail, () => DateTime.Now) as ICommand;
|
||||
|
||||
/// <summary> Gets a command object to perform copri queries. </summary>
|
||||
private static IQuery GetQuery(ICachedCopriServer copri, string sessioncookie, string mail) => string.IsNullOrEmpty(sessioncookie)
|
||||
? new CachedQuery(copri) as IQuery
|
||||
: new CachedQueryLoggedIn(copri, sessioncookie, mail, () => DateTime.Now);
|
||||
}
|
||||
}
|
47
TINKLib/Model/Connector/ConnectorCache.cs
Normal file
47
TINKLib/Model/Connector/ConnectorCache.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
using TINK.Model.Services.CopriApi;
|
||||
using TINK.Model.Repository;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
/// <summary>
|
||||
/// Connects tink app to copri by getting data from copri and updating tink app model (i.e. bikes, user, ...)
|
||||
/// </summary>
|
||||
public class ConnectorCache : IConnector
|
||||
{
|
||||
/// <summary>Constructs a copri connector object.</summary>
|
||||
/// <param name="p_strSessionCookie"> Holds the session cookie.</param>
|
||||
/// <param name="p_strMail">Mail of user.</param>
|
||||
/// <param name="server"> Provides addess to copri.</param>
|
||||
public ConnectorCache(
|
||||
string sessionCookie,
|
||||
string mail,
|
||||
ICopriServer server = null)
|
||||
{
|
||||
|
||||
Command = Connector.GetCommand(
|
||||
server ?? new CopriProviderMonkeyStore(TinkApp.MerchantId, sessionCookie),
|
||||
sessionCookie,
|
||||
mail);
|
||||
|
||||
Query = GetQuery(
|
||||
server ?? new CopriProviderMonkeyStore(TinkApp.MerchantId, sessionCookie),
|
||||
sessionCookie,
|
||||
mail);
|
||||
}
|
||||
|
||||
/// <summary> Object for queriying stations and bikes.</summary>
|
||||
public ICommand Command { get; private set; }
|
||||
|
||||
/// <summary> Object for queriying stations and bikes.</summary>
|
||||
public IQuery Query { get; private set; }
|
||||
|
||||
/// <summary> True if connector has access to copri server, false if cached values are used. </summary>
|
||||
public bool IsConnected => Command.IsConnected;
|
||||
|
||||
/// <summary> Gets a command object to perform copri queries. </summary>
|
||||
private static IQuery GetQuery(ICopriServer copri, string sessioncookie, string mail) => string.IsNullOrEmpty(sessioncookie)
|
||||
? new Query(copri) as IQuery
|
||||
: new QueryLoggedIn(copri, sessioncookie, mail, () => DateTime.Now);
|
||||
}
|
||||
}
|
19
TINKLib/Model/Connector/ConnectorFactory.cs
Normal file
19
TINKLib/Model/Connector/ConnectorFactory.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
public class ConnectorFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a connector object depending on whether beein onlin or offline.
|
||||
/// </summary>
|
||||
/// <param name="isConnected">True if online, false if offline</param>
|
||||
/// <returns></returns>
|
||||
public static IConnector Create(bool isConnected, Uri activeUri, string userAgent, string sessionCookie, string mail, TimeSpan? expiresAfter = null)
|
||||
{
|
||||
return isConnected
|
||||
? new Connector(activeUri, userAgent, sessionCookie, mail, expiresAfter: expiresAfter) as IConnector
|
||||
: new ConnectorCache(sessionCookie, mail);
|
||||
}
|
||||
}
|
||||
}
|
13
TINKLib/Model/Connector/Filter/GroupFilterFactory.cs
Normal file
13
TINKLib/Model/Connector/Filter/GroupFilterFactory.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TINK.Model.Connector.Filter
|
||||
{
|
||||
public static class GroupFilterFactory
|
||||
{
|
||||
public static IGroupFilter Create(IEnumerable<string> group)
|
||||
{
|
||||
return group != null ? (IGroupFilter) new IntersectGroupFilter(group) : new NullGroupFilter();
|
||||
}
|
||||
}
|
||||
}
|
9
TINKLib/Model/Connector/Filter/IGroupFilter.cs
Normal file
9
TINKLib/Model/Connector/Filter/IGroupFilter.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace TINK.Model.Connector.Filter
|
||||
{
|
||||
public interface IGroupFilter
|
||||
{
|
||||
IEnumerable<string> DoFilter(IEnumerable<string> filter);
|
||||
}
|
||||
}
|
20
TINKLib/Model/Connector/Filter/IntersectGroupFilter.cs
Normal file
20
TINKLib/Model/Connector/Filter/IntersectGroupFilter.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TINK.Model.Connector.Filter
|
||||
{
|
||||
/// <summary> Filters to enumerations of string by intersecting.</summary>
|
||||
public class IntersectGroupFilter : IGroupFilter
|
||||
{
|
||||
private IEnumerable<string> Group { get; set; }
|
||||
|
||||
public IntersectGroupFilter(IEnumerable<string> group) => Group = group ?? new List<string>();
|
||||
|
||||
/// <summary> Applies filtering. </summary>
|
||||
/// <param name="filter">Enumeration of filter values to filter with or null if no filtering has to be applied.</param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<string> DoFilter(IEnumerable<string> filter) => filter != null
|
||||
? Group.Intersect(filter)
|
||||
: Group;
|
||||
}
|
||||
}
|
9
TINKLib/Model/Connector/Filter/NullGroupFilter.cs
Normal file
9
TINKLib/Model/Connector/Filter/NullGroupFilter.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace TINK.Model.Connector.Filter
|
||||
{
|
||||
public class NullGroupFilter : IGroupFilter
|
||||
{
|
||||
public IEnumerable<string> DoFilter(IEnumerable<string> filter) => filter;
|
||||
}
|
||||
}
|
11
TINKLib/Model/Connector/FilterHelper.cs
Normal file
11
TINKLib/Model/Connector/FilterHelper.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace TINK.Model.Connector
|
||||
{
|
||||
public static class FilterHelper
|
||||
{
|
||||
/// <summary> Holds the Konrad group (city bikes).</summary>
|
||||
public const string FILTERKONRAD = "Konrad";
|
||||
|
||||
/// <summary> Holds the tink group (Lastenräder).</summary>
|
||||
public const string FILTERTINKGENERAL = "TINK";
|
||||
}
|
||||
}
|
115
TINKLib/Model/Connector/FilteredConnector.cs
Normal file
115
TINKLib/Model/Connector/FilteredConnector.cs
Normal file
|
@ -0,0 +1,115 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Bike;
|
||||
using TINK.Model.Connector.Filter;
|
||||
using TINK.Model.Services.CopriApi;
|
||||
using TINK.Model.Station;
|
||||
|
||||
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
/// <summary> Filters connector respones.</summary>
|
||||
/// <remarks>Former name: Filter</remarks>
|
||||
public class FilteredConnector : IFilteredConnector
|
||||
{
|
||||
/// <summary> Constructs a filter object. </summary>
|
||||
/// <param name="group">Filter group.</param>
|
||||
/// <param name="connector">Connector object.</param>
|
||||
public FilteredConnector(
|
||||
IEnumerable<string> group,
|
||||
IConnector connector)
|
||||
{
|
||||
Connector = connector;
|
||||
|
||||
if (Connector == null)
|
||||
{
|
||||
throw new ArgumentException("Can not construct filter object. Connector- and command objects must not be null.");
|
||||
}
|
||||
|
||||
Query = new QueryProvider(Connector.Query, GroupFilterFactory.Create(group));
|
||||
}
|
||||
|
||||
/// <summary> Inner connector object.</summary>
|
||||
public IConnector Connector { get; }
|
||||
|
||||
/// <summary> Command object. </summary>
|
||||
public ICommand Command => Connector.Command;
|
||||
|
||||
/// <summary> Object to query information. </summary>
|
||||
public IQuery Query { get; }
|
||||
|
||||
/// <summary> True if connector has access to copri server, false if cached values are used. </summary>
|
||||
public bool IsConnected => Connector.IsConnected;
|
||||
|
||||
/// <summary> Object to perform filtered queries.</summary>
|
||||
private class QueryProvider : IQuery
|
||||
{
|
||||
/// <summary> Holds the filter. </summary>
|
||||
private IGroupFilter Filter { get; }
|
||||
|
||||
/// <summary> Holds the reference to object which performs copry queries.</summary>
|
||||
private IQuery m_oInnerQuery;
|
||||
|
||||
/// <summary> Constructs a query object.</summary>
|
||||
/// <param name="innerQuerry"></param>
|
||||
/// <param name="filter"></param>
|
||||
public QueryProvider(IQuery innerQuerry, IGroupFilter filter)
|
||||
{
|
||||
m_oInnerQuery = innerQuerry;
|
||||
Filter = filter;
|
||||
}
|
||||
|
||||
/// <summary> Gets bikes either bikes available if no user is logged in or bikes available and bikes occupied if a user is logged in. </summary>
|
||||
public async Task<Result<BikeCollection>> GetBikesAsync()
|
||||
{
|
||||
var result = await m_oInnerQuery.GetBikesAsync();
|
||||
return new Result<BikeCollection>(
|
||||
result.Source,
|
||||
new BikeCollection(DoFilter(result.Response, Filter)),
|
||||
result.Exception);
|
||||
}
|
||||
|
||||
/// <summary> Gets bikes occupied if a user is logged in. </summary>
|
||||
public async Task<Result<BikeCollection>> GetBikesOccupiedAsync()
|
||||
{
|
||||
var result = await m_oInnerQuery.GetBikesOccupiedAsync();
|
||||
return new Result<BikeCollection>(
|
||||
result.Source,
|
||||
new BikeCollection(result.Response.ToDictionary(x => x.Id)),
|
||||
result.Exception);
|
||||
}
|
||||
|
||||
/// <summary> Gets all station applying filter rules. </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
|
||||
{
|
||||
var result = 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);
|
||||
}
|
||||
|
||||
/// <summary> Filter bikes by group. </summary>
|
||||
/// <param name="p_oBikes">Bikes to filter.</param>
|
||||
/// <returns>Filtered bikes.</returns>
|
||||
private static Dictionary<int, BikeInfo> DoFilter(BikeCollection p_oBikes, IGroupFilter filter)
|
||||
{
|
||||
return p_oBikes.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)
|
||||
{
|
||||
return p_oStations.Where(x => filter.DoFilter(x.Group).Count() > 0).ToDictionary((x => x.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
16
TINKLib/Model/Connector/FilteredConnectorFactory.cs
Normal file
16
TINKLib/Model/Connector/FilteredConnectorFactory.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
public static class FilteredConnectorFactory
|
||||
{
|
||||
/// <summary> Creates a filter object. </summary>
|
||||
/// <param name="group"></param>
|
||||
public static IFilteredConnector Create(IEnumerable<string> group, IConnector connector)
|
||||
{
|
||||
return group != null
|
||||
? (IFilteredConnector) new FilteredConnector(group, connector)
|
||||
: new NullFilterConnector(connector);
|
||||
}
|
||||
}
|
||||
}
|
14
TINKLib/Model/Connector/IConnector.cs
Normal file
14
TINKLib/Model/Connector/IConnector.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace TINK.Model.Connector
|
||||
{
|
||||
public interface IConnector
|
||||
{
|
||||
/// <summary> Object for queriying stations and bikes.</summary>
|
||||
ICommand Command { get; }
|
||||
|
||||
/// <summary> Object for queriying stations and bikes.</summary>
|
||||
IQuery Query { get; }
|
||||
|
||||
/// <summary> True if connector has access to copri server, false if cached values are used. </summary>
|
||||
bool IsConnected { get; }
|
||||
}
|
||||
}
|
7
TINKLib/Model/Connector/IFilteredConnector.cs
Normal file
7
TINKLib/Model/Connector/IFilteredConnector.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace TINK.Model.Connector
|
||||
{
|
||||
public interface IFilteredConnector : IConnector
|
||||
{
|
||||
IConnector Connector { get; }
|
||||
}
|
||||
}
|
108
TINKLib/Model/Connector/NullFilterConnector.cs
Normal file
108
TINKLib/Model/Connector/NullFilterConnector.cs
Normal file
|
@ -0,0 +1,108 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Bike;
|
||||
using TINK.Model.Services.CopriApi;
|
||||
using TINK.Model.Station;
|
||||
|
||||
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
/// <summary> Filters connector respones.</summary>
|
||||
public class NullFilterConnector : IFilteredConnector
|
||||
{
|
||||
/// <summary> Constructs a filter object. </summary>
|
||||
/// <param name="p_oGroup">Filter group.</param>
|
||||
/// <param name="connector">Connector object.</param>
|
||||
public NullFilterConnector(
|
||||
IConnector connector)
|
||||
{
|
||||
Connector = connector;
|
||||
|
||||
if (Connector == null)
|
||||
{
|
||||
throw new ArgumentException("Can not construct filter object. Connector- and command objects must not be null.");
|
||||
}
|
||||
|
||||
Query = new QueryProvider(Connector.Query);
|
||||
}
|
||||
|
||||
/// <summary> Inner connector object.</summary>
|
||||
public IConnector Connector { get; }
|
||||
|
||||
/// <summary> Command object. </summary>
|
||||
public ICommand Command => Connector.Command;
|
||||
|
||||
/// <summary> Object to query information. </summary>
|
||||
public IQuery Query { get; }
|
||||
|
||||
/// <summary> True if connector has access to copri server, false if cached values are used. </summary>
|
||||
public bool IsConnected => Connector.IsConnected;
|
||||
|
||||
/// <summary> Object to perform filtered queries.</summary>
|
||||
private class QueryProvider : IQuery
|
||||
{
|
||||
/// <summary> Holds the reference to object which performs copry queries.</summary>
|
||||
private IQuery m_oInnerQuery;
|
||||
|
||||
/// <summary> Constructs a query object.</summary>
|
||||
/// <param name="p_oInnerQuery"></param>
|
||||
/// <param name="p_oFilter"></param>
|
||||
public QueryProvider(IQuery p_oInnerQuery)
|
||||
{
|
||||
m_oInnerQuery = p_oInnerQuery;
|
||||
}
|
||||
|
||||
/// <summary> Gets bikes either bikes available if no user is logged in or bikes available and bikes occupied if a user is logged in. </summary>
|
||||
public async Task<Result<BikeCollection>> GetBikesAsync()
|
||||
{
|
||||
var result = await m_oInnerQuery.GetBikesAsync();
|
||||
return new Result<BikeCollection>(
|
||||
result.Source,
|
||||
new BikeCollection(result.Response.ToDictionary(x => x.Id)),
|
||||
result.Exception);
|
||||
}
|
||||
|
||||
/// <summary> Gets bikes occupied if a user is logged in. </summary>
|
||||
public async Task<Result<BikeCollection>> GetBikesOccupiedAsync()
|
||||
{
|
||||
var result = await m_oInnerQuery.GetBikesOccupiedAsync();
|
||||
return new Result<BikeCollection>(
|
||||
result.Source,
|
||||
new BikeCollection(result.Response.ToDictionary(x => x.Id)),
|
||||
result.Exception);
|
||||
}
|
||||
|
||||
/// <summary> Gets all station applying filter rules. </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
|
||||
{
|
||||
var result = await m_oInnerQuery.GetBikesAndStationsAsync();
|
||||
|
||||
return new Result<StationsAndBikesContainer>(
|
||||
result.Source,
|
||||
new StationsAndBikesContainer(
|
||||
new StationDictionary(result.Response.StationsAll.CopriVersion, result.Response.StationsAll.ToDictionary(x => x.Id)),
|
||||
new BikeCollection(result.Response.Bikes.ToDictionary(x => x.Id))),
|
||||
result.Exception);
|
||||
}
|
||||
|
||||
/// <summary> Filter bikes by group. </summary>
|
||||
/// <param name="p_oBikes">Bikes to filter.</param>
|
||||
/// <returns>Filtered bikes.</returns>
|
||||
public static Dictionary<int, BikeInfo> DoFilter(BikeCollection p_oBikes, IEnumerable<string> p_oFilter)
|
||||
{
|
||||
return p_oBikes.Where(x => x.Group.Intersect(p_oFilter).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)
|
||||
{
|
||||
return p_oStations.Where(x => x.Group.Intersect(p_oFilter).Count() > 0).ToDictionary((x => x.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
TINKLib/Model/Connector/Query/Base.cs
Normal file
27
TINKLib/Model/Connector/Query/Base.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using TINK.Model.Repository;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides information required for copri commands/ query operations.
|
||||
/// </summary>
|
||||
public class Base
|
||||
{
|
||||
/// <summary> Reference to object which provides access to copri server. </summary>
|
||||
protected ICopriServerBase CopriServer { get; }
|
||||
|
||||
/// <summary> Gets the merchant id.</summary>
|
||||
protected string MerchantId => CopriServer.MerchantId;
|
||||
|
||||
/// <summary> Constructs a query base object.</summary>
|
||||
/// <param name="p_oCopriServer">Server which implements communication.</param>
|
||||
/// <param name="p_oErrorStack">Object which hold communication objects.</param>
|
||||
protected Base(
|
||||
ICopriServerBase p_oCopriServer)
|
||||
{
|
||||
CopriServer = p_oCopriServer
|
||||
?? throw new ArgumentException("Can not instantiate command/ query base- object. Copri server object must never be null or emtpy.");
|
||||
}
|
||||
}
|
||||
}
|
39
TINKLib/Model/Connector/Query/BaseLoggedIn.cs
Normal file
39
TINKLib/Model/Connector/Query/BaseLoggedIn.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using TINK.Model.Repository;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
/// <summary>Holds user infromation required for copri related commands/ query operations. </summary>
|
||||
public class BaseLoggedIn : Base
|
||||
{
|
||||
/// <summary>Session cookie used to sign in to copri.</summary>
|
||||
public string SessionCookie { get; }
|
||||
|
||||
/// <summary> Mail address of the user. </summary>
|
||||
protected string Mail { get; }
|
||||
|
||||
/// <summary> Object which provides date time info. </summary>
|
||||
protected readonly Func<DateTime> DateTimeProvider;
|
||||
|
||||
/// <summary>Constructs a copri query object.</summary>
|
||||
/// <param name="p_oCopriServer">Server which implements communication.</param>
|
||||
public BaseLoggedIn(ICopriServerBase p_oCopriServer,
|
||||
string p_strSessionCookie,
|
||||
string p_strMail,
|
||||
Func<DateTime> p_oDateTimeProvider) : base(p_oCopriServer)
|
||||
{
|
||||
if (string.IsNullOrEmpty(p_strSessionCookie))
|
||||
throw new ArgumentException("Can not instantiate query object- object. Session cookie must never be null or emtpy.");
|
||||
|
||||
if (string.IsNullOrEmpty(p_strMail))
|
||||
throw new ArgumentException("Can not instantiate query object- object. Mail address must never be null or emtpy.");
|
||||
|
||||
DateTimeProvider = p_oDateTimeProvider
|
||||
?? throw new ArgumentException("Can not instantiate connector- object. No date time provider object available.");
|
||||
|
||||
SessionCookie = p_strSessionCookie;
|
||||
|
||||
Mail = p_strMail;
|
||||
}
|
||||
}
|
||||
}
|
86
TINKLib/Model/Connector/Query/CachedQuery.cs
Normal file
86
TINKLib/Model/Connector/Query/CachedQuery.cs
Normal file
|
@ -0,0 +1,86 @@
|
|||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Bike;
|
||||
using TINK.Model.Services.CopriApi;
|
||||
using TINK.Model.Repository;
|
||||
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
public class CachedQuery : Base, IQuery
|
||||
{
|
||||
/// <summary> Cached copri server. </summary>
|
||||
private readonly ICachedCopriServer server;
|
||||
|
||||
/// <summary>Constructs a copri query object.</summary>
|
||||
/// <param name="p_oCopriServer">Server which implements communication.</param>
|
||||
public CachedQuery(
|
||||
ICopriServerBase p_oCopriServer) : base(p_oCopriServer)
|
||||
{
|
||||
server = p_oCopriServer as ICachedCopriServer;
|
||||
if (server == null)
|
||||
{
|
||||
throw new ArgumentException($"Copri server is not of expected typ. Type detected is {p_oCopriServer.GetType()}.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets all stations including postions and bikes.</summary>
|
||||
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
|
||||
{
|
||||
var resultStations = await server.GetStations();
|
||||
|
||||
if (resultStations.Source == typeof(CopriCallsMonkeyStore))
|
||||
{
|
||||
// Communication with copri in order to get stations failed.
|
||||
return new Result<StationsAndBikesContainer>(
|
||||
resultStations.Source,
|
||||
new StationsAndBikesContainer(
|
||||
resultStations.Response.GetStationsAllMutable(),
|
||||
(await server.GetBikesAvailable(true)).Response.GetBikesAvailable()),
|
||||
resultStations.Exception);
|
||||
}
|
||||
|
||||
var resultBikes = await server.GetBikesAvailable();
|
||||
if (resultBikes.Source == typeof(CopriCallsMonkeyStore))
|
||||
{
|
||||
// Communication with copri in order to get bikes failed.
|
||||
return new Result<StationsAndBikesContainer>(
|
||||
resultBikes.Source,
|
||||
new StationsAndBikesContainer(
|
||||
(await server.GetStations(true)).Response.GetStationsAllMutable(),
|
||||
resultBikes.Response.GetBikesAvailable()),
|
||||
resultBikes.Exception);
|
||||
}
|
||||
|
||||
// Communicatin with copri succeeded.
|
||||
server.AddToCache(resultStations);
|
||||
server.AddToCache(resultBikes);
|
||||
|
||||
return new Result<StationsAndBikesContainer>(
|
||||
resultStations.Source,
|
||||
new StationsAndBikesContainer(resultStations.Response.GetStationsAllMutable(), resultBikes.Response.GetBikesAvailable()));
|
||||
}
|
||||
|
||||
/// <summary> Gets bikes occupied. </summary>
|
||||
/// <returns>Collection of bikes.</returns>
|
||||
public async Task<Result<BikeCollection>> GetBikesOccupiedAsync()
|
||||
{
|
||||
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>())),
|
||||
new System.Exception("Abfrage der reservierten/ gebuchten Räder nicht möglich. Kein Benutzer angemeldet."));
|
||||
}
|
||||
|
||||
/// <summary> Gets bikes available. </summary>
|
||||
/// <returns>Collection of bikes.</returns>
|
||||
public async Task<Result<BikeCollection>> GetBikesAsync()
|
||||
{
|
||||
var result = await server.GetBikesAvailable();
|
||||
server.AddToCache(result);
|
||||
return new Result<BikeCollection>(result.Source, result.Response.GetBikesAvailable(), result.Exception);
|
||||
}
|
||||
}
|
||||
}
|
159
TINKLib/Model/Connector/Query/CachedQueryLoggedIn.cs
Normal file
159
TINKLib/Model/Connector/Query/CachedQueryLoggedIn.cs
Normal file
|
@ -0,0 +1,159 @@
|
|||
using MonkeyCache.FileStore;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Bike;
|
||||
using TINK.Model.Services.CopriApi;
|
||||
using TINK.Model.Repository;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
/// <summary> Provides query functionality for a logged in user. </summary>
|
||||
public class CachedQueryLoggedIn : BaseLoggedIn, IQuery
|
||||
{
|
||||
/// <summary> Cached copri server. </summary>
|
||||
private readonly ICachedCopriServer server;
|
||||
|
||||
/// <summary>Constructs a copri query object.</summary>
|
||||
/// <param name="p_oCopriServer">Server which implements communication.</param>
|
||||
public CachedQueryLoggedIn(ICopriServerBase p_oCopriServer,
|
||||
string p_strSessionCookie,
|
||||
string p_strMail,
|
||||
Func<DateTime> p_oDateTimeProvider) : base(p_oCopriServer, p_strSessionCookie, p_strMail, p_oDateTimeProvider)
|
||||
{
|
||||
server = p_oCopriServer as ICachedCopriServer;
|
||||
if (server == null)
|
||||
{
|
||||
throw new ArgumentException($"Copri server is not of expected typ. Type detected is {p_oCopriServer.GetType()}.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets all stations including postions.</summary>
|
||||
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
|
||||
{
|
||||
var resultStations = await server.GetStations();
|
||||
|
||||
if (resultStations.Source == typeof(CopriCallsMonkeyStore)
|
||||
|| resultStations.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,
|
||||
new StationsAndBikesContainer(
|
||||
resultStations.Response.GetStationsAllMutable(),
|
||||
UpdaterJSON.GetBikesAll(
|
||||
(await server.GetBikesAvailable(true)).Response,
|
||||
(await server.GetBikesOccupied(true)).Response,
|
||||
Mail,
|
||||
DateTimeProvider)),
|
||||
resultStations.Exception);
|
||||
}
|
||||
|
||||
var l_oBikesAvailableResponse = await server.GetBikesAvailable();
|
||||
if (l_oBikesAvailableResponse.Source == typeof(CopriCallsMonkeyStore)
|
||||
|| l_oBikesAvailableResponse.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,
|
||||
new StationsAndBikesContainer(
|
||||
(await server.GetStations(true)).Response.GetStationsAllMutable(),
|
||||
UpdaterJSON.GetBikesAll(l_oBikesAvailableResponse.Response,
|
||||
(await server.GetBikesOccupied(true)).Response,
|
||||
Mail,
|
||||
DateTimeProvider)),
|
||||
l_oBikesAvailableResponse.Exception);
|
||||
}
|
||||
|
||||
var l_oBikesOccupiedResponse = await server.GetBikesOccupied();
|
||||
if (l_oBikesOccupiedResponse.Source == typeof(CopriCallsMonkeyStore)
|
||||
|| l_oBikesOccupiedResponse.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,
|
||||
new StationsAndBikesContainer(
|
||||
(await server.GetStations(true)).Response.GetStationsAllMutable(),
|
||||
UpdaterJSON.GetBikesAll(
|
||||
(await server.GetBikesAvailable(true)).Response,
|
||||
l_oBikesOccupiedResponse.Response,
|
||||
Mail,
|
||||
DateTimeProvider)),
|
||||
l_oBikesOccupiedResponse.Exception);
|
||||
}
|
||||
|
||||
// Both types bikes could read from copri => update cache
|
||||
server.AddToCache(resultStations);
|
||||
server.AddToCache(l_oBikesAvailableResponse);
|
||||
server.AddToCache(l_oBikesOccupiedResponse);
|
||||
|
||||
var exceptions = new[] { resultStations?.Exception, l_oBikesAvailableResponse?.Exception, l_oBikesOccupiedResponse?.Exception }.Where(x => x != null).ToArray();
|
||||
|
||||
return new Result<StationsAndBikesContainer>(
|
||||
resultStations.Source,
|
||||
new StationsAndBikesContainer(
|
||||
resultStations.Response.GetStationsAllMutable(),
|
||||
UpdaterJSON.GetBikesAll(
|
||||
l_oBikesAvailableResponse.Response,
|
||||
l_oBikesOccupiedResponse.Response,
|
||||
Mail,
|
||||
DateTimeProvider)),
|
||||
exceptions.Length > 0 ? new AggregateException(exceptions) : null);
|
||||
}
|
||||
|
||||
/// <summary> Gets bikes occupied. </summary>
|
||||
/// <returns>Collection of bikes.</returns>
|
||||
public async Task<Result<BikeCollection>> GetBikesOccupiedAsync()
|
||||
{
|
||||
var result = await server.GetBikesOccupied();
|
||||
server.AddToCache(result);
|
||||
return new Result<BikeCollection>(result.Source, result.Response.GetBikesOccupied(Mail, DateTimeProvider), result.Exception);
|
||||
}
|
||||
|
||||
/// <summary> Gets bikes available and bikes occupied. </summary>
|
||||
/// <returns>Collection of bikes.</returns>
|
||||
public async Task<Result<BikeCollection>> GetBikesAsync()
|
||||
{
|
||||
var l_oBikesAvailableResponse = await server.GetBikesAvailable();
|
||||
|
||||
if (l_oBikesAvailableResponse.Source == typeof(CopriCallsMonkeyStore)
|
||||
|| l_oBikesAvailableResponse.Exception != null)
|
||||
{
|
||||
// Bikes avilable were read from cache ==> get bikes occupied from cache as well to avoid inconsistencies
|
||||
return new Result<BikeCollection>(
|
||||
l_oBikesAvailableResponse.Source,
|
||||
UpdaterJSON.GetBikesAll(
|
||||
l_oBikesAvailableResponse.Response,
|
||||
(await server.GetBikesOccupied(true)).Response,
|
||||
Mail,
|
||||
DateTimeProvider),
|
||||
l_oBikesAvailableResponse.Exception);
|
||||
}
|
||||
|
||||
var l_oBikesOccupiedResponse = await server.GetBikesOccupied();
|
||||
if (l_oBikesOccupiedResponse.Source == typeof(CopriCallsMonkeyStore)
|
||||
|| l_oBikesOccupiedResponse.Exception != null)
|
||||
{
|
||||
// Bikes occupied were read from cache ==> get bikes available from cache as well to avoid inconsistencies
|
||||
return new Result<BikeCollection>(
|
||||
l_oBikesOccupiedResponse.Source,
|
||||
UpdaterJSON.GetBikesAll(
|
||||
(await server.GetBikesAvailable(true)).Response,
|
||||
l_oBikesOccupiedResponse.Response,
|
||||
Mail,
|
||||
DateTimeProvider),
|
||||
l_oBikesOccupiedResponse.Exception);
|
||||
|
||||
}
|
||||
|
||||
// Both types bikes could read from copri => update cache
|
||||
server.AddToCache(l_oBikesAvailableResponse);
|
||||
server.AddToCache(l_oBikesOccupiedResponse);
|
||||
|
||||
return new Result<BikeCollection>(
|
||||
l_oBikesAvailableResponse.Source,
|
||||
UpdaterJSON.GetBikesAll(l_oBikesAvailableResponse.Response, l_oBikesOccupiedResponse.Response, Mail, DateTimeProvider),
|
||||
l_oBikesAvailableResponse.Exception != null || l_oBikesOccupiedResponse.Exception != null ? new AggregateException(new[] { l_oBikesAvailableResponse.Exception, l_oBikesOccupiedResponse.Exception }) : null);
|
||||
}
|
||||
}
|
||||
}
|
20
TINKLib/Model/Connector/Query/IQuery.cs
Normal file
20
TINKLib/Model/Connector/Query/IQuery.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using System.Threading.Tasks;
|
||||
using TINK.Model.Bike;
|
||||
using TINK.Model.Services.CopriApi;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
public interface IQuery
|
||||
{
|
||||
/// <summary> Gets all stations including postions.</summary>
|
||||
Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync();
|
||||
|
||||
/// <summary> Gets bikes occupied is a user is logged in. </summary>
|
||||
/// <returns>Collection of bikes.</returns>
|
||||
Task<Result<BikeCollection>> GetBikesOccupiedAsync();
|
||||
|
||||
/// <summary> Gets bikes either bikes available if no user is logged in or bikes available and bikes occupied if a user is logged in. </summary>
|
||||
/// <returns>Collection of bikes.</returns>
|
||||
Task<Result<BikeCollection>> GetBikesAsync();
|
||||
}
|
||||
}
|
60
TINKLib/Model/Connector/Query/Query.cs
Normal file
60
TINKLib/Model/Connector/Query/Query.cs
Normal file
|
@ -0,0 +1,60 @@
|
|||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Bike;
|
||||
using TINK.Model.Services.CopriApi;
|
||||
using TINK.Model.Repository;
|
||||
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
/// <summary> Provides query functionality without login. </summary>
|
||||
public class Query : Base, IQuery
|
||||
{
|
||||
/// <summary> Cached copri server. </summary>
|
||||
private readonly ICopriServer server;
|
||||
|
||||
/// <summary>Constructs a copri query object.</summary>
|
||||
/// <param name="p_oCopriServer">Server which implements communication.</param>
|
||||
public Query(ICopriServerBase p_oCopriServer) : base(p_oCopriServer)
|
||||
{
|
||||
server = p_oCopriServer as ICopriServer;
|
||||
if (server == null)
|
||||
{
|
||||
throw new ArgumentException($"Copri server is not of expected typ. Type detected is {p_oCopriServer.GetType()}.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets all stations including postions.</summary>
|
||||
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
|
||||
{
|
||||
var stationsAllResponse = await server.GetStationsAsync();
|
||||
var bikesAvailableResponse = await server.GetBikesAvailableAsync();
|
||||
|
||||
return new Result<StationsAndBikesContainer>(
|
||||
typeof(CopriCallsMonkeyStore),
|
||||
new StationsAndBikesContainer( stationsAllResponse.GetStationsAllMutable(), bikesAvailableResponse.GetBikesAvailable()));
|
||||
}
|
||||
|
||||
/// <summary> Gets bikes occupied. </summary>
|
||||
/// <returns>Collection of bikes.</returns>
|
||||
public async Task<Result<BikeCollection>> GetBikesOccupiedAsync()
|
||||
{
|
||||
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>())),
|
||||
new System.Exception("Abfrage der reservierten/ gebuchten Räder fehlgeschlagen. Kein Benutzer angemeldet."));
|
||||
}
|
||||
|
||||
/// <summary> Gets bikes occupied. </summary>
|
||||
/// <returns> Collection of bikes. </returns>
|
||||
public async Task<Result<BikeCollection>> GetBikesAsync()
|
||||
{
|
||||
return new Result<BikeCollection>(
|
||||
typeof(CopriCallsMonkeyStore),
|
||||
(await server.GetBikesAvailableAsync()).GetBikesAvailable());
|
||||
}
|
||||
}
|
||||
}
|
65
TINKLib/Model/Connector/Query/QueryLoggedIn.cs
Normal file
65
TINKLib/Model/Connector/Query/QueryLoggedIn.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Bike;
|
||||
using TINK.Model.Services.CopriApi;
|
||||
using TINK.Model.Repository;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
/// <summary> Provides query functionality for a logged in user. </summary>
|
||||
public class QueryLoggedIn : BaseLoggedIn, IQuery
|
||||
{
|
||||
/// <summary> Cached copri server. </summary>
|
||||
private readonly ICopriServer server;
|
||||
|
||||
/// <summary>Constructs a copri query object.</summary>
|
||||
/// <param name="p_oCopriServer">Server which implements communication.</param>
|
||||
public QueryLoggedIn(ICopriServerBase p_oCopriServer,
|
||||
string p_strSessionCookie,
|
||||
string p_strMail,
|
||||
Func<DateTime> p_oDateTimeProvider) : base(p_oCopriServer, p_strSessionCookie, p_strMail, p_oDateTimeProvider)
|
||||
{
|
||||
server = p_oCopriServer as ICopriServer;
|
||||
if (server == null)
|
||||
{
|
||||
throw new ArgumentException($"Copri server is not of expected typ. Type detected is {p_oCopriServer.GetType()}.");
|
||||
}
|
||||
|
||||
server = p_oCopriServer as ICopriServer;
|
||||
}
|
||||
|
||||
/// <summary> Gets all stations including postions.</summary>
|
||||
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
|
||||
{
|
||||
var stationResponse = await server.GetStationsAsync();
|
||||
var bikesAvailableResponse = await server.GetBikesAvailableAsync();
|
||||
var bikesOccupiedResponse = await server.GetBikesOccupiedAsync();
|
||||
|
||||
return new Result<StationsAndBikesContainer>(
|
||||
typeof(CopriCallsMonkeyStore),
|
||||
new StationsAndBikesContainer(
|
||||
stationResponse.GetStationsAllMutable(),
|
||||
UpdaterJSON.GetBikesAll(bikesAvailableResponse, bikesOccupiedResponse, Mail, DateTimeProvider)));
|
||||
}
|
||||
|
||||
/// <summary> Gets bikes occupied. </summary>
|
||||
/// <returns>Collection of bikes.</returns>
|
||||
public async Task<Result<BikeCollection>> GetBikesOccupiedAsync()
|
||||
{
|
||||
return new Result<BikeCollection>(
|
||||
typeof(CopriCallsMonkeyStore),
|
||||
(await server.GetBikesOccupiedAsync()).GetBikesOccupied(Mail, DateTimeProvider));
|
||||
}
|
||||
/// <summary> Gets bikes available and bikes occupied. </summary>
|
||||
/// <returns>Collection of bikes.</returns>
|
||||
public async Task<Result<BikeCollection>> GetBikesAsync()
|
||||
{
|
||||
var l_oBikesAvailableResponse = await server.GetBikesAvailableAsync();
|
||||
var l_oBikesOccupiedResponse = await server.GetBikesOccupiedAsync();
|
||||
|
||||
return new Result<BikeCollection>(
|
||||
typeof(CopriCallsMonkeyStore),
|
||||
UpdaterJSON.GetBikesAll(l_oBikesAvailableResponse, l_oBikesOccupiedResponse, Mail, DateTimeProvider));
|
||||
}
|
||||
}
|
||||
}
|
358
TINKLib/Model/Connector/TextToTypeHelper.cs
Normal file
358
TINKLib/Model/Connector/TextToTypeHelper.cs
Normal file
|
@ -0,0 +1,358 @@
|
|||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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.Model.Services.CopriApi.ServerUris;
|
||||
using TINK.Model.State;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
/// <summary>
|
||||
/// Conversion helper functionality.
|
||||
/// </summary>
|
||||
public static class TextToTypeHelper
|
||||
{
|
||||
/// <summary> Holds the text for demo bikes. </summary>
|
||||
private const string DEMOBIKEMARKER = "DEMO";
|
||||
|
||||
/// <summary> Part text denoting two wheel cargo bike.. </summary>
|
||||
private const string TWOWHEELCARGOMARKERFRAGMENT = "LONG";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position from StationInfo object.
|
||||
/// </summary>
|
||||
/// <param name="p_oStationInfo">Object to get information from.</param>
|
||||
/// <returns>Position information.</returns>
|
||||
public static Station.Position GetPosition(this StationsAllResponse.StationInfo p_oStationInfo)
|
||||
{
|
||||
return GetPosition(p_oStationInfo.gps);
|
||||
}
|
||||
|
||||
/// <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 AuthorizationResponse p_oAuthorizationResponse)
|
||||
{
|
||||
try
|
||||
{
|
||||
return p_oAuthorizationResponse.user_group.GetGroup();
|
||||
}
|
||||
catch (Exception l_oException)
|
||||
{
|
||||
throw new Exception($"Can not get group of user from text \"{p_oAuthorizationResponse.user_group}\".", l_oException);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (string.IsNullOrEmpty(p_oGroup))
|
||||
{
|
||||
throw new ArgumentException("Can not get goup form string. Group text can not be null.");
|
||||
}
|
||||
|
||||
return new HashSet<string>(p_oGroup.Split(',')).ToList();
|
||||
}
|
||||
|
||||
/// <summary> Gets the position from StationInfo object. </summary>
|
||||
/// <param name="p_oAuthorizationResponse">Object to get information from.</param>
|
||||
/// <returns>Position information.</returns>
|
||||
public static string GetGroup(this IEnumerable<string> p_oGroup)
|
||||
{
|
||||
return string.Join(",", p_oGroup);
|
||||
}
|
||||
|
||||
/// <summary> Gets the position from StationInfo object. </summary>
|
||||
/// <param name="p_oStationInfo">Object to get information from.</param>
|
||||
/// <returns>Position information.</returns>
|
||||
public static IEnumerable<string> GetGroup(this StationsAllResponse.StationInfo p_oStationInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
return p_oStationInfo.station_group.GetGroup();
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
throw new System.Exception($"Can not get group of stations from text \"{p_oStationInfo.station_group}\".", l_oException);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position from StationInfo object.
|
||||
/// </summary>
|
||||
/// <param name="p_oBikeInfo">Object to get information from.</param>
|
||||
/// <returns>Position information.</returns>
|
||||
public static Station.Position GetPosition(this BikeInfoAvailable p_oBikeInfo)
|
||||
{
|
||||
return GetPosition(p_oBikeInfo.gps);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position from StationInfo object.
|
||||
/// </summary>
|
||||
/// <param name="p_oBikeInfo">Object to get information from.</param>
|
||||
/// <returns>Position information.</returns>
|
||||
public static InUseStateEnum GetState(this BikeInfoBase p_oBikeInfo)
|
||||
{
|
||||
var l_oState = p_oBikeInfo.state;
|
||||
|
||||
if (string.IsNullOrEmpty(l_oState))
|
||||
{
|
||||
throw new InvalidResponseException<BikeInfoBase>(
|
||||
string.Format("Unknown reservation state detected. Member {0}.{1}.", typeof(BikeInfoBase), nameof(BikeInfoBase.state)),
|
||||
p_oBikeInfo);
|
||||
}
|
||||
|
||||
if (l_oState == "available")
|
||||
{
|
||||
return InUseStateEnum.Disposable;
|
||||
}
|
||||
else if (l_oState == "reserved" ||
|
||||
l_oState == "requested")
|
||||
{
|
||||
return InUseStateEnum.Reserved;
|
||||
}
|
||||
else if (l_oState == "booked" ||
|
||||
l_oState == "occupied")
|
||||
{
|
||||
return InUseStateEnum.Booked;
|
||||
}
|
||||
|
||||
throw new CommunicationException(string.Format("Unknown bike state detected. State is {0}.", l_oState));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the from date information from JSON.
|
||||
/// </summary>
|
||||
/// <param name="p_oBikeInfo">JSON to get information from..</param>
|
||||
/// <returns>From information.</returns>
|
||||
public static DateTime GetFrom(this BikeInfoReservedOrBooked p_oBikeInfo)
|
||||
{
|
||||
return DateTime.Parse(p_oBikeInfo.start_time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the bike is a trike or not.
|
||||
/// </summary>
|
||||
/// <param name="p_oBikeInfo">JSON to get information from..</param>
|
||||
/// <returns>From information.</returns>
|
||||
public static bool? GetIsDemo(this BikeInfoBase p_oBikeInfo)
|
||||
{
|
||||
return p_oBikeInfo?.description != null
|
||||
? p_oBikeInfo.description.ToUpper().Contains(DEMOBIKEMARKER)
|
||||
: (bool?) null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the bike is a trike or not.
|
||||
/// </summary>
|
||||
/// <param name="p_oBikeInfo">JSON to get information from..</param>
|
||||
/// <returns>From information.</returns>
|
||||
public static IEnumerable<string> GetGroup(this BikeInfoBase p_oBikeInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
return p_oBikeInfo?.bike_group?.GetGroup()?.ToList() ?? new List<string>();
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
throw new System.Exception($"Can not get group of user from text \"{p_oBikeInfo.bike_group}\".", l_oException);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets whether the bike has a bord computer or not. </summary>
|
||||
/// <param name="p_oBikeInfo">JSON to get information from.</param>
|
||||
/// <returns>From information.</returns>
|
||||
public static bool GetIsManualLockBike(this BikeInfoBase p_oBikeInfo)
|
||||
{
|
||||
return !string.IsNullOrEmpty(p_oBikeInfo.system)
|
||||
&& p_oBikeInfo.system.ToUpper().StartsWith("LOCK");
|
||||
}
|
||||
|
||||
/// <summary> Gets whether the bike has a bord computer or not. </summary>
|
||||
/// <param name="p_oBikeInfo">JSON to get information from..</param>
|
||||
/// <returns>From information.</returns>
|
||||
public static bool GetIsBluetoothLockBike(this BikeInfoBase p_oBikeInfo)
|
||||
{
|
||||
return !string.IsNullOrEmpty(p_oBikeInfo.system)
|
||||
&& p_oBikeInfo.system.ToUpper().StartsWith("ILOCKIT");
|
||||
}
|
||||
|
||||
/// <summary> Gets whether the bike has a bord computer or not. </summary>
|
||||
/// <param name="bikeInfo">JSON to get information from..</param>
|
||||
/// <returns>From information.</returns>
|
||||
public static int GetBluetoothLockId(this BikeInfoAvailable bikeInfo)
|
||||
{
|
||||
return TextToLockItTypeHelper.GetBluetoothLockId(bikeInfo?.Ilockit_ID);
|
||||
}
|
||||
|
||||
/// <summary> Gets whether the bike has a bord computer or not. </summary>
|
||||
/// <param name="bikeInfo">JSON to get information from..</param>
|
||||
/// <returns>From information.</returns>
|
||||
public static Guid GetBluetoothLockGuid(this BikeInfoAvailable bikeInfo)
|
||||
{
|
||||
// return new Guid("00000000-0000-0000-0000-e57e6b9aee16");
|
||||
return Guid.TryParse(bikeInfo?.Ilockit_GUID, out Guid lockGuid)
|
||||
? lockGuid
|
||||
: TextToLockItTypeHelper.INVALIDLOCKGUID;
|
||||
}
|
||||
|
||||
public static byte[] GetUserKey(this BikeInfoReservedOrBooked bikeInfo)
|
||||
{
|
||||
return GetKey(bikeInfo.K_u);
|
||||
}
|
||||
|
||||
public static byte[] GetAdminKey(this BikeInfoReservedOrBooked bikeInfo)
|
||||
{
|
||||
return GetKey(bikeInfo.K_a);
|
||||
}
|
||||
|
||||
public static byte[] GetSeed(this BikeInfoReservedOrBooked bikeInfo)
|
||||
{
|
||||
return GetKey(bikeInfo.K_seed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get array of keys from string of format "[12, -9, 5]"
|
||||
/// </summary>
|
||||
/// <param name="keyArrayText"></param>
|
||||
/// <returns></returns>
|
||||
private static byte[] GetKey(string keyArrayText)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(keyArrayText))
|
||||
return new byte[0];
|
||||
|
||||
return Regex.Replace(keyArrayText, @"\[(.*)\]", "$1").Split(',').Select(x => (byte)sbyte.Parse(x)).ToArray();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error("Can not extract K_u/ K_a/ or K_seed. Key {ArrayText} does not is not of expected format. {Exception}", keyArrayText, exception);
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the bike is a trike or not.
|
||||
/// </summary>
|
||||
/// <param name="bikeInfo">JSON to get information from..</param>
|
||||
/// <returns>From information.</returns>
|
||||
public static WheelType? GetWheelType(this BikeInfoBase bikeInfo)
|
||||
{
|
||||
var l_oDescription = bikeInfo.description;
|
||||
|
||||
if (l_oDescription == null)
|
||||
{
|
||||
// Can not get type of wheel if description text is empty.
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (WheelType l_oWheelType in Enum.GetValues(typeof(WheelType)))
|
||||
{
|
||||
|
||||
if (l_oDescription.ToUpper().Contains(l_oWheelType.ToString().ToUpper()))
|
||||
{
|
||||
return l_oWheelType;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for custom value "Long".
|
||||
if (l_oDescription.ToUpper().Contains(TWOWHEELCARGOMARKERFRAGMENT))
|
||||
{
|
||||
return WheelType.Two;
|
||||
}
|
||||
|
||||
// Check for Stadrad.
|
||||
if (GetTypeOfBike(bikeInfo) == TypeOfBike.Citybike)
|
||||
{
|
||||
return WheelType.Two;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of bike.
|
||||
/// </summary>
|
||||
/// <param name="bikeInfo">Object to get bike type from.</param>
|
||||
/// <returns>Type of bike.</returns>
|
||||
public static TypeOfBike? GetTypeOfBike(this BikeInfoBase bikeInfo)
|
||||
{
|
||||
var l_oDescription = bikeInfo?.description;
|
||||
|
||||
if (l_oDescription == null)
|
||||
{
|
||||
// Can not get type of wheel if description text is empty.
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (TypeOfBike l_oTypeOfBike in Enum.GetValues(typeof(TypeOfBike)))
|
||||
{
|
||||
if (l_oDescription.ToUpper().Contains(l_oTypeOfBike.GetCopriText().ToUpper()))
|
||||
{
|
||||
return l_oTypeOfBike;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get position from a ,- separated string.
|
||||
/// </summary>
|
||||
/// <param name="p_strGps">Text to extract positon from.</param>
|
||||
/// <returns>Position object.</returns>
|
||||
public static Station.Position GetPosition(string p_strGps)
|
||||
{
|
||||
if (p_strGps == 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))
|
||||
return null;
|
||||
|
||||
double l_oLongitude;
|
||||
if (!double.TryParse(l_oPosition[1], NumberStyles.Float, CultureInfo.InvariantCulture, out l_oLongitude))
|
||||
return null;
|
||||
|
||||
return new Station.Position(l_oLatitude, l_oLongitude);
|
||||
}
|
||||
|
||||
/// <summary> Gets text of bike from. </summary>
|
||||
/// <param name="p_eType">Type to get text for.</param>
|
||||
/// <returns></returns>
|
||||
public static string GetCopriText(this TypeOfBike p_eType)
|
||||
{
|
||||
switch (p_eType)
|
||||
{
|
||||
case TypeOfBike.Citybike:
|
||||
return "Stadtrad";
|
||||
|
||||
default:
|
||||
return p_eType.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static Uri GetOperatorUri(this BikeInfoBase bikeInfo)
|
||||
{
|
||||
return bikeInfo?.uri_operator != null && !string.IsNullOrEmpty(bikeInfo?.uri_operator)
|
||||
? new Uri($"{bikeInfo.uri_operator}/{CopriServerUriList.REST_RESOURCE_ROOT}")
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
496
TINKLib/Model/Connector/Updater/UpdaterJSON.cs
Normal file
496
TINKLib/Model/Connector/Updater/UpdaterJSON.cs
Normal file
|
@ -0,0 +1,496 @@
|
|||
using System;
|
||||
using TINK.Model.Bike;
|
||||
using TINK.Model.Station;
|
||||
using TINK.Model.Repository.Response;
|
||||
using TINK.Model.User.Account;
|
||||
using System.Collections.Generic;
|
||||
using TINK.Model.State;
|
||||
using TINK.Model.Repository.Exception;
|
||||
using Serilog;
|
||||
|
||||
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
|
||||
using IBikeInfoMutable = TINK.Model.Bikes.Bike.BC.IBikeInfoMutable;
|
||||
using System.Globalization;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
/// <summary>
|
||||
/// Connects TINK app to copri using JSON as input data format.
|
||||
/// </summary>
|
||||
/// <todo>Rename to UpdateFromCopri.</todo>
|
||||
public static class UpdaterJSON
|
||||
{
|
||||
/// <summary> Loads a bike object from copri server cancel reservation/ booking update request.</summary>
|
||||
/// <param name="bike">Bike object to load response into.</param>
|
||||
/// <param name="notifyLevel">Controls whether notify property changed events are fired or not.</param>
|
||||
public static void Load(
|
||||
this IBikeInfoMutable bike,
|
||||
Bikes.Bike.BC.NotifyPropertyChangedLevel notifyLevel)
|
||||
{
|
||||
|
||||
bike.State.Load(InUseStateEnum.Disposable, notifyLevel: notifyLevel);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets all statsion for station provider and add them into station list.
|
||||
/// </summary>
|
||||
/// <param name="p_oStationList">List of stations to update.</param>
|
||||
public static StationDictionary GetStationsAllMutable(this StationsAllResponse p_oStationsAllResponse)
|
||||
{
|
||||
// Get stations from Copri/ file/ memory, ....
|
||||
if (p_oStationsAllResponse == null
|
||||
|| p_oStationsAllResponse.stations == null)
|
||||
{
|
||||
// Latest list of stations could not be retrieved from provider.
|
||||
return new StationDictionary();
|
||||
}
|
||||
|
||||
Version.TryParse(p_oStationsAllResponse.copri_version, out Version l_oCopriVersion);
|
||||
|
||||
var l_oStations = new StationDictionary(p_oVersion: l_oCopriVersion);
|
||||
|
||||
foreach (var l_oStation in p_oStationsAllResponse.stations)
|
||||
{
|
||||
if (l_oStations.GetById(l_oStation.Value.station) != null)
|
||||
{
|
||||
// Can not add station to list of station. Id is not unique.
|
||||
throw new InvalidResponseException<StationsAllResponse>(
|
||||
string.Format("Station id {0} is not unique.", l_oStation.Value.station), p_oStationsAllResponse);
|
||||
}
|
||||
|
||||
l_oStations.Add(new Station.Station(
|
||||
l_oStation.Value.station,
|
||||
l_oStation.Value.GetGroup(),
|
||||
l_oStation.Value.GetPosition(),
|
||||
l_oStation.Value.description));
|
||||
}
|
||||
|
||||
return l_oStations;
|
||||
}
|
||||
|
||||
/// <summary> Gets account object from login response.</summary>
|
||||
/// <param name="merchantId">Needed to extract cookie from autorization response.</param>
|
||||
/// <param name="loginResponse">Response to get session cookie and debug level from.</param>
|
||||
/// <param name="mail">Mail address needed to construct a complete account object (is not part of response).</param>
|
||||
/// <param name="password">Password needed to construct a complete account object (is not part of response).</param>
|
||||
public static IAccount GetAccount(
|
||||
this AuthorizationResponse loginResponse,
|
||||
string merchantId,
|
||||
string mail,
|
||||
string password)
|
||||
{
|
||||
if (loginResponse == null)
|
||||
{
|
||||
throw new ArgumentNullException("p_oLoginResponse");
|
||||
}
|
||||
|
||||
return new Account(
|
||||
mail,
|
||||
password,
|
||||
loginResponse.authcookie?.Replace(merchantId, ""),
|
||||
loginResponse.GetGroup(),
|
||||
loginResponse.debuglevel == 1
|
||||
? Permissions.All :
|
||||
(Permissions)loginResponse.debuglevel) ;
|
||||
}
|
||||
|
||||
/// <summary> Load bike object from booking response. </summary>
|
||||
/// <param name="bike">Bike object to load from response.</param>
|
||||
/// <param name="bikeInfo">Booking response.</param>
|
||||
/// <param name="mailAddress">Mail address of user which books bike.</param>
|
||||
/// <param name="p_strSessionCookie">Session cookie of user which books bike.</param>
|
||||
/// <param name="notifyLevel">Controls whether notify property changed events are fired or not.</param>
|
||||
public static void Load(
|
||||
this IBikeInfoMutable bike,
|
||||
BikeInfoReservedOrBooked bikeInfo,
|
||||
string mailAddress,
|
||||
Func<DateTime> dateTimeProvider,
|
||||
Bikes.Bike.BC.NotifyPropertyChangedLevel notifyLevel = Bikes.Bike.BC.NotifyPropertyChangedLevel.All)
|
||||
{
|
||||
|
||||
var l_oDateTimeProvider = dateTimeProvider != null
|
||||
? dateTimeProvider
|
||||
: () => DateTime.Now;
|
||||
|
||||
if (bike is Bike.BluetoothLock.BikeInfoMutable btBikeInfo)
|
||||
{
|
||||
btBikeInfo.LockInfo.Load(
|
||||
bikeInfo.GetBluetoothLockId(),
|
||||
bikeInfo.GetBluetoothLockGuid(),
|
||||
bikeInfo.GetSeed(),
|
||||
bikeInfo.GetUserKey(),
|
||||
bikeInfo.GetAdminKey());
|
||||
}
|
||||
|
||||
var l_oState = bikeInfo.GetState();
|
||||
switch (l_oState)
|
||||
{
|
||||
case InUseStateEnum.Disposable:
|
||||
bike.State.Load(
|
||||
InUseStateEnum.Disposable,
|
||||
notifyLevel: notifyLevel);
|
||||
break;
|
||||
|
||||
case InUseStateEnum.Reserved:
|
||||
bike.State.Load(
|
||||
InUseStateEnum.Reserved,
|
||||
bikeInfo.GetFrom(),
|
||||
mailAddress,
|
||||
bikeInfo.timeCode,
|
||||
notifyLevel);
|
||||
break;
|
||||
|
||||
case InUseStateEnum.Booked:
|
||||
bike.State.Load(
|
||||
InUseStateEnum.Booked,
|
||||
bikeInfo.GetFrom(),
|
||||
mailAddress,
|
||||
bikeInfo.timeCode,
|
||||
notifyLevel);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception(string.Format("Unexpected bike state detected. state is {0}.", l_oState));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets bikes available from copri server response.</summary>
|
||||
/// <param name="p_oBikesAvailableResponse">Response to create collection from.</param>
|
||||
/// <returns>New collection of available bikes.</returns>
|
||||
public static BikeCollection GetBikesAvailable(
|
||||
this BikesAvailableResponse p_oBikesAvailableResponse)
|
||||
{
|
||||
return GetBikesAll(
|
||||
p_oBikesAvailableResponse,
|
||||
new BikesReservedOccupiedResponse(), // There are no occupied bikes.
|
||||
string.Empty,
|
||||
() => DateTime.Now);
|
||||
}
|
||||
|
||||
/// <summary> Gets bikes occupied from copri server response. </summary>
|
||||
/// <param name="p_oBikesAvailable">Response to create bikes from.</param>
|
||||
/// <returns>New collection of occupied bikes.</returns>
|
||||
public static BikeCollection GetBikesOccupied(
|
||||
this BikesReservedOccupiedResponse p_oBikesOccupiedResponse,
|
||||
string p_strMail,
|
||||
Func<DateTime> p_oDateTimeProvider)
|
||||
{
|
||||
return GetBikesAll(
|
||||
new BikesAvailableResponse(),
|
||||
p_oBikesOccupiedResponse,
|
||||
p_strMail,
|
||||
p_oDateTimeProvider);
|
||||
}
|
||||
|
||||
/// <summary> Gets bikes occupied from copri server response. </summary>
|
||||
/// <param name="p_oBikesAvailable">Response to create bikes from.</param>
|
||||
/// <returns>New collection of occupied bikes.</returns>
|
||||
public static BikeCollection GetBikesAll(
|
||||
BikesAvailableResponse p_oBikesAvailableResponse,
|
||||
BikesReservedOccupiedResponse p_oBikesOccupiedResponse,
|
||||
string p_strMail,
|
||||
Func<DateTime> p_oDateTimeProvider)
|
||||
{
|
||||
var l_oBikesDictionary = new Dictionary<int, BikeInfo>();
|
||||
var l_oDuplicates = new Dictionary<int, BikeInfo>();
|
||||
|
||||
// Get bikes from Copri/ file/ memory, ....
|
||||
if (p_oBikesAvailableResponse != null
|
||||
&& p_oBikesAvailableResponse.bikes != null)
|
||||
{
|
||||
foreach (var bikeInfoResponse in p_oBikesAvailableResponse.bikes.Values)
|
||||
{
|
||||
var l_oBikeInfo = BikeInfoFactory.Create(bikeInfoResponse);
|
||||
if (l_oBikeInfo == null)
|
||||
{
|
||||
// Response is not valid.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (l_oBikesDictionary.ContainsKey(l_oBikeInfo.Id))
|
||||
{
|
||||
// Duplicates are not allowed.
|
||||
Log.Error($"Duplicate bike with id {l_oBikeInfo.Id} detected evaluating bikes available. Bike status is {l_oBikeInfo.State.Value}.");
|
||||
|
||||
if (!l_oDuplicates.ContainsKey(l_oBikeInfo.Id))
|
||||
{
|
||||
l_oDuplicates.Add(l_oBikeInfo.Id, l_oBikeInfo);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
l_oBikesDictionary.Add(l_oBikeInfo.Id, l_oBikeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// Get bikes from Copri/ file/ memory, ....
|
||||
if (p_oBikesOccupiedResponse != null
|
||||
&& p_oBikesOccupiedResponse.bikes_occupied != null)
|
||||
{
|
||||
foreach (var l_oBikeInfoResponse in p_oBikesOccupiedResponse.bikes_occupied.Values)
|
||||
{
|
||||
BikeInfo l_oBikeInfo = BikeInfoFactory.Create(
|
||||
l_oBikeInfoResponse,
|
||||
p_strMail,
|
||||
p_oDateTimeProvider);
|
||||
|
||||
if (l_oBikeInfo == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (l_oBikesDictionary.ContainsKey(l_oBikeInfo.Id))
|
||||
{
|
||||
// Duplicates are not allowed.
|
||||
Log.Error($"Duplicate bike with id {l_oBikeInfo.Id} detected evaluating bikes occupied. Bike status is {l_oBikeInfo.State.Value}.");
|
||||
if (!l_oDuplicates.ContainsKey(l_oBikeInfo.Id))
|
||||
{
|
||||
l_oDuplicates.Add(l_oBikeInfo.Id, l_oBikeInfo);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
l_oBikesDictionary.Add(l_oBikeInfo.Id, l_oBikeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove entries which are not unique.
|
||||
foreach (var l_oDuplicate in l_oDuplicates)
|
||||
{
|
||||
l_oBikesDictionary.Remove(l_oDuplicate.Key);
|
||||
}
|
||||
|
||||
return new BikeCollection(l_oBikesDictionary);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructs bike info instances/ bike info derived instances.
|
||||
/// </summary>
|
||||
public static class BikeInfoFactory
|
||||
{
|
||||
public static BikeInfo Create(BikeInfoAvailable bikeInfo)
|
||||
{
|
||||
if (bikeInfo.GetIsManualLockBike())
|
||||
{
|
||||
// Manual lock bikes are no more supported.
|
||||
Log.Error(
|
||||
$"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. " +
|
||||
"Manual lock bikes are no more supported." +
|
||||
$"Bike number: {bikeInfo.bike}{(bikeInfo.station != null ? $"station number {bikeInfo.station}" : string.Empty)}."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (bikeInfo.GetState())
|
||||
{
|
||||
case InUseStateEnum.Disposable:
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.Error($"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. Unexpected state {bikeInfo.GetState()} detected.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (bikeInfo.station == null)
|
||||
{
|
||||
// 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.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return !bikeInfo.GetIsBluetoothLockBike()
|
||||
? new BikeInfo(
|
||||
bikeInfo.bike,
|
||||
bikeInfo.station,
|
||||
bikeInfo.GetOperatorUri(),
|
||||
#if !NOTARIFFDESCRIPTION
|
||||
Create(bikeInfo.tariff_description),
|
||||
#else
|
||||
Create((TINK.Repository.Response.TariffDescription) null),
|
||||
#endif
|
||||
bikeInfo.GetIsDemo(),
|
||||
bikeInfo.GetGroup(),
|
||||
bikeInfo.GetWheelType(),
|
||||
bikeInfo.GetTypeOfBike(),
|
||||
bikeInfo.description)
|
||||
: new Bike.BluetoothLock.BikeInfo(
|
||||
bikeInfo.bike,
|
||||
bikeInfo.GetBluetoothLockId(),
|
||||
bikeInfo.GetBluetoothLockGuid(),
|
||||
bikeInfo.station,
|
||||
bikeInfo.GetOperatorUri(),
|
||||
#if !NOTARIFFDESCRIPTION
|
||||
Create(bikeInfo.tariff_description),
|
||||
#else
|
||||
Create((TINK.Repository.Response.TariffDescription)null),
|
||||
#endif
|
||||
bikeInfo.GetIsDemo(),
|
||||
bikeInfo.GetGroup(),
|
||||
bikeInfo.GetWheelType(),
|
||||
bikeInfo.GetTypeOfBike(),
|
||||
bikeInfo.description);
|
||||
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
// Contructor reported invalid arguemts (missing lock id, ....).
|
||||
Log.Error($"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. Invalid response detected. Available bike with id {bikeInfo.bike} skipped. {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Creates a bike info object from copri response. </summary>
|
||||
/// <param name="bikeInfo">Copri response. </param>
|
||||
/// <param name="mailAddress">Mail address of user.</param>
|
||||
/// <param name="dateTimeProvider">Date and time provider function.</param>
|
||||
/// <returns></returns>
|
||||
public static BikeInfo Create(
|
||||
BikeInfoReservedOrBooked bikeInfo,
|
||||
string mailAddress,
|
||||
Func<DateTime> dateTimeProvider)
|
||||
{
|
||||
if (bikeInfo.GetIsManualLockBike())
|
||||
{
|
||||
// Manual lock bikes are no more supported.
|
||||
Log.Error(
|
||||
$"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. " +
|
||||
"Manual lock bikes are no more supported." +
|
||||
$"Bike number: {bikeInfo.bike}{(bikeInfo.station != null ? $", station number {bikeInfo.station}" : string.Empty)}."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if bike is a bluetooth lock bike.
|
||||
var isBluetoothBike = bikeInfo.GetIsBluetoothLockBike();
|
||||
int lockSerial = bikeInfo.GetBluetoothLockId();
|
||||
Guid lockGuid = bikeInfo.GetBluetoothLockGuid();
|
||||
|
||||
switch (bikeInfo.GetState())
|
||||
{
|
||||
case InUseStateEnum.Reserved:
|
||||
try
|
||||
{
|
||||
return !isBluetoothBike
|
||||
? new BikeInfo(
|
||||
bikeInfo.bike,
|
||||
bikeInfo.GetIsDemo(),
|
||||
bikeInfo.GetGroup(),
|
||||
bikeInfo.GetWheelType(),
|
||||
bikeInfo.GetTypeOfBike(),
|
||||
bikeInfo.description,
|
||||
bikeInfo.station,
|
||||
bikeInfo.GetOperatorUri(),
|
||||
|
||||
#if !NOTARIFFDESCRIPTION
|
||||
Create(bikeInfo.tariff_description),
|
||||
#else
|
||||
Create((TINK.Repository.Response.TariffDescription)null),
|
||||
#endif
|
||||
bikeInfo.GetFrom(),
|
||||
mailAddress,
|
||||
bikeInfo.timeCode,
|
||||
dateTimeProvider)
|
||||
: new Bike.BluetoothLock.BikeInfo(
|
||||
bikeInfo.bike,
|
||||
lockSerial,
|
||||
lockGuid,
|
||||
bikeInfo.GetUserKey(),
|
||||
bikeInfo.GetAdminKey(),
|
||||
bikeInfo.GetSeed(),
|
||||
bikeInfo.GetFrom(),
|
||||
mailAddress,
|
||||
bikeInfo.station,
|
||||
bikeInfo.GetOperatorUri(),
|
||||
#if !NOTARIFFDESCRIPTION
|
||||
Create(bikeInfo.tariff_description),
|
||||
#else
|
||||
Create((TINK.Repository.Response.TariffDescription)null),
|
||||
#endif
|
||||
dateTimeProvider,
|
||||
bikeInfo.GetIsDemo(),
|
||||
bikeInfo.GetGroup(),
|
||||
bikeInfo.GetWheelType(),
|
||||
bikeInfo.GetTypeOfBike());
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
// Contructor reported invalid arguemts (missing lock id, ....).
|
||||
Log.Error($"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoReservedOrBooked)} argument. Invalid response detected. Reserved bike with id {bikeInfo.bike} skipped. {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
|
||||
case InUseStateEnum.Booked:
|
||||
try
|
||||
{
|
||||
return !isBluetoothBike
|
||||
? new BikeInfo(
|
||||
bikeInfo.bike,
|
||||
bikeInfo.GetIsDemo(),
|
||||
bikeInfo.GetGroup(),
|
||||
bikeInfo.GetWheelType(),
|
||||
bikeInfo.GetTypeOfBike(),
|
||||
bikeInfo.description,
|
||||
bikeInfo.station,
|
||||
bikeInfo.GetOperatorUri(),
|
||||
#if !NOTARIFFDESCRIPTION
|
||||
Create(bikeInfo.tariff_description),
|
||||
#else
|
||||
Create((TINK.Repository.Response.TariffDescription)null),
|
||||
#endif
|
||||
bikeInfo.GetFrom(),
|
||||
mailAddress,
|
||||
bikeInfo.timeCode)
|
||||
: new Bike.BluetoothLock.BikeInfo(
|
||||
bikeInfo.bike,
|
||||
lockSerial,
|
||||
bikeInfo.GetBluetoothLockGuid(),
|
||||
bikeInfo.GetUserKey(),
|
||||
bikeInfo.GetAdminKey(),
|
||||
bikeInfo.GetSeed(),
|
||||
bikeInfo.GetFrom(),
|
||||
mailAddress,
|
||||
bikeInfo.station,
|
||||
bikeInfo.GetOperatorUri(),
|
||||
#if !NOTARIFFDESCRIPTION
|
||||
Create(bikeInfo.tariff_description),
|
||||
#else
|
||||
Create((TINK.Repository.Response.TariffDescription)null),
|
||||
#endif
|
||||
bikeInfo.GetIsDemo(),
|
||||
bikeInfo.GetGroup(),
|
||||
bikeInfo.GetWheelType(),
|
||||
bikeInfo.GetTypeOfBike());
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
// Contructor reported invalid arguemts (missing lock id, ....).
|
||||
Log.Error($"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoReservedOrBooked)} argument. Invalid response detected. Booked bike with id {bikeInfo.bike} skipped. {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
|
||||
default:
|
||||
Log.Error($"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. Unexpected state {bikeInfo.GetState()} detected.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Bikes.Bike.TariffDescription Create(this TINK.Repository.Response.TariffDescription tariffDesciption)
|
||||
{
|
||||
return new Bikes.Bike.TariffDescription
|
||||
{
|
||||
Name = tariffDesciption?.name,
|
||||
Number = int.TryParse(tariffDesciption?.number, out int number) ? number : null,
|
||||
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,
|
||||
MaxFeeEuroPerDay = double.TryParse(tariffDesciption?.max_eur_per_day, NumberStyles.Any, CultureInfo.InvariantCulture, out double maxEuroPerDay) ? maxEuroPerDay : double.NaN,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
14
TINKLib/Model/Device/IAppInfo.cs
Normal file
14
TINKLib/Model/Device/IAppInfo.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
namespace TINK.Model.Device
|
||||
{
|
||||
/// <summary> Interface to get version info. </summary>
|
||||
public interface IAppInfo
|
||||
{
|
||||
/// <summary> Gets the app version to display to user.</summary>
|
||||
Version Version { get; }
|
||||
|
||||
/// <summary> Gets the URL to the app store. </summary>
|
||||
/// <value>The store URL.</value>
|
||||
string StoreUrl { get; }
|
||||
}
|
||||
}
|
9
TINKLib/Model/Device/IDevice.cs
Normal file
9
TINKLib/Model/Device/IDevice.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace TINK.Model.Device
|
||||
{
|
||||
public interface IDevice
|
||||
{
|
||||
/// <summary> Gets unitque device identifier. </summary>
|
||||
/// <returns>Gets the identifies specifying device.</returns>
|
||||
string GetIdentifier();
|
||||
}
|
||||
}
|
9
TINKLib/Model/Device/IExternalBrowserService.cs
Normal file
9
TINKLib/Model/Device/IExternalBrowserService.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace TINK.Model.Device
|
||||
{
|
||||
public interface IExternalBrowserService
|
||||
{
|
||||
/// <summary> Opens an external browser. </summary>
|
||||
/// <param name="url">Url to open.</param>
|
||||
void OpenUrl(string url);
|
||||
}
|
||||
}
|
7
TINKLib/Model/Device/IGeolodationDependent.cs
Normal file
7
TINKLib/Model/Device/IGeolodationDependent.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace TINK.Model.Device
|
||||
{
|
||||
public interface IGeolodationDependent
|
||||
{
|
||||
bool IsGeolcationEnabled { get; }
|
||||
}
|
||||
}
|
15
TINKLib/Model/Device/ISpecialFolder.cs
Normal file
15
TINKLib/Model/Device/ISpecialFolder.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
namespace TINK.Model.Device
|
||||
{
|
||||
public interface ISpecialFolder
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the folder name of external folder to write to.
|
||||
/// </summary>
|
||||
/// <returns>External directory.</returns>
|
||||
string GetExternalFilesDir();
|
||||
|
||||
/// <summary> Gets the folder name of the personal data folder dir on internal storage. </summary>
|
||||
/// <returns>Directory name.</returns>
|
||||
string GetInternalPersonalDir();
|
||||
}
|
||||
}
|
8
TINKLib/Model/Device/IWebView.cs
Normal file
8
TINKLib/Model/Device/IWebView.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace TINK.Model.Device
|
||||
{
|
||||
public interface IWebView
|
||||
{
|
||||
/// <summary> Clears the cookie cache for all web views. </summary>
|
||||
void ClearCookies();
|
||||
}
|
||||
}
|
11
TINKLib/Model/FileOperationException.cs
Normal file
11
TINKLib/Model/FileOperationException.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace TINK.Model
|
||||
{
|
||||
/// <summary> Operation fired when a file operation fails.</summary>
|
||||
public class FileOperationException : Exception
|
||||
{
|
||||
public FileOperationException(string message, Exception innerException) : base(message, innerException)
|
||||
{ }
|
||||
}
|
||||
}
|
14
TINKLib/Model/FilterCollectionStore.cs
Normal file
14
TINKLib/Model/FilterCollectionStore.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TINK.Model
|
||||
{
|
||||
public static class FilterCollectionStore
|
||||
{
|
||||
/// <summary> Writes filter collection state to string. </summary>
|
||||
/// <returns> Filter collection to write to string.</returns>
|
||||
/// <param name="p_oDictionary">P o dictionary.</param>
|
||||
public static string ToString(this IDictionary<string, FilterState> p_oDictionary) => "{" + string.Join(", ", p_oDictionary.Select(x => "(" + x.Key + "= " + x.Value + ")")) + "}";
|
||||
}
|
||||
}
|
45
TINKLib/Model/GroupFilterHelper.cs
Normal file
45
TINKLib/Model/GroupFilterHelper.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using System.Collections.Generic;
|
||||
using TINK.Model.Connector;
|
||||
using TINK.ViewModel.Map;
|
||||
using TINK.ViewModel.Settings;
|
||||
|
||||
namespace TINK.Model
|
||||
{
|
||||
/// <summary> Holds collecion of filters to filter options (TINK, Konrad, ....). </summary>
|
||||
/// <remarks> Former name: FilterCollection.</remarks>
|
||||
public static class GroupFilterHelper
|
||||
{
|
||||
/// <summary> Gets default filter set.</summary>
|
||||
public static IGroupFilterSettings GetSettingsFilterDefaults
|
||||
{
|
||||
get
|
||||
{
|
||||
return new GroupFilterSettings(new Dictionary<string, FilterState> {
|
||||
{ FilterHelper.FILTERTINKGENERAL, FilterState.On },
|
||||
{FilterHelper.FILTERKONRAD, FilterState.On }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets default filter set.</summary>
|
||||
public static IGroupFilterMapPage GetMapPageFilterDefaults
|
||||
{
|
||||
get
|
||||
{
|
||||
return new ViewModel.Map.GroupFilterMapPage(new Dictionary<string, FilterState> {
|
||||
{ FilterHelper.FILTERTINKGENERAL, FilterState.On },
|
||||
{FilterHelper.FILTERKONRAD, FilterState.Off }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Holds value whether filter (on TINK, Konrad, ....) is on or off. </summary>
|
||||
public enum FilterState
|
||||
{
|
||||
/// <summary> Option (TINK, Konrad, ....) is available.</summary>
|
||||
On,
|
||||
/// <summary> Option is off</summary>
|
||||
Off,
|
||||
}
|
||||
}
|
98
TINKLib/Model/ITinkApp.cs
Normal file
98
TINKLib/Model/ITinkApp.cs
Normal file
|
@ -0,0 +1,98 @@
|
|||
using Plugin.Permissions.Abstractions;
|
||||
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;
|
||||
|
||||
namespace TINK.Model
|
||||
{
|
||||
public interface ITinkApp
|
||||
{
|
||||
/// <summary> Update connector from depending on whether user is logged in or not.</summary>
|
||||
void UpdateConnector();
|
||||
|
||||
/// <summary> Saves object to file. </summary>
|
||||
void Save();
|
||||
|
||||
/// <summary> Holds the filter which is applied on the map view. Either TINK or Konrad stations are displayed. </summary>
|
||||
IGroupFilterMapPage GroupFilterMapPage { get; set; }
|
||||
|
||||
/// <summary> Holds the user of the app. </summary>
|
||||
User.User ActiveUser { get; }
|
||||
|
||||
/// <summary>Sets flag whats new page was already shown to true. </summary>
|
||||
void SetWhatsNewWasShown();
|
||||
|
||||
/// <summary> Holds the system to copri.</summary>
|
||||
IFilteredConnector GetConnector(bool isConnected);
|
||||
|
||||
/// <summary> Name of the station which is selected. </summary>
|
||||
int? SelectedStation { get; set; }
|
||||
|
||||
/// <summary>Polling periode.</summary>
|
||||
PollingParameters Polling { get; set; }
|
||||
|
||||
TimeSpan ExpiresAfter { get; set; }
|
||||
|
||||
/// <summary> Holds status about whants new page. </summary>
|
||||
WhatsNew WhatsNew { get; }
|
||||
|
||||
/// <summary> Gets whether device is connected to internet or not. </summary>
|
||||
bool GetIsConnected();
|
||||
|
||||
/// <summary> Action to post to GUI thread.</summary>
|
||||
Action<SendOrPostCallback, object> PostAction { get; }
|
||||
|
||||
/// <summary> Holds the uri which is applied after restart. </summary>
|
||||
Uri NextActiveUri { get; set; }
|
||||
|
||||
/// <summary> Holds the filters loaded from settings. </summary>
|
||||
IGroupFilterSettings FilterGroupSetting { get; set; }
|
||||
|
||||
/// <summary> Value indicating whether map is centerted to current position or not. </summary>
|
||||
bool CenterMapToCurrentLocation { get; set; }
|
||||
|
||||
bool LogToExternalFolder { get; set; }
|
||||
|
||||
bool IsSiteCachingOn { get; set; }
|
||||
|
||||
/// <summary> Gets the minimum logging level. </summary>
|
||||
LogEventLevel MinimumLogEventLevel { get; set; }
|
||||
|
||||
/// <summary> Updates logging level. </summary>
|
||||
/// <param name="p_oNewLevel">New level to set.</param>
|
||||
void UpdateLoggingLevel(LogEventLevel p_oNewLevel);
|
||||
|
||||
/// <summary>Holds uris of copri servers. </summary>
|
||||
CopriServerUriList Uris { get; }
|
||||
|
||||
/// <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; }
|
||||
|
||||
/// <summary> Holds the folder where settings files are stored. </summary>
|
||||
string SettingsFileFolder { get; }
|
||||
|
||||
/// <summary> Holds the external path. </summary>
|
||||
string ExternalFolder { get; }
|
||||
}
|
||||
}
|
20
TINKLib/Model/Logging/EmptyDirectoryLoggingManger.cs
Normal file
20
TINKLib/Model/Logging/EmptyDirectoryLoggingManger.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace TINK.Model.Logging
|
||||
{
|
||||
public class EmptyDirectoryLoggingManger : ILoggingDirectoryManager
|
||||
{
|
||||
public void DeleteObsoleteLogs() { }
|
||||
|
||||
/// <summary> Gets the log file name. </summary>
|
||||
public string LogFileName { get { return string.Empty; } }
|
||||
|
||||
/// <summary> Gets all log files in logging directory. </summary>
|
||||
/// <returns>List of log files.</returns>
|
||||
public IList<string> GetLogFiles() { return new List<string>(); }
|
||||
|
||||
/// <summary> Gets path where log files are located. </summary>
|
||||
/// <returns>Path to log files.</returns>
|
||||
public string LogFilePath { get { return string.Empty; } }
|
||||
}
|
||||
}
|
21
TINKLib/Model/Logging/ILoggingDirectoryManager.cs
Normal file
21
TINKLib/Model/Logging/ILoggingDirectoryManager.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace TINK.Model.Logging
|
||||
{
|
||||
public interface ILoggingDirectoryManager
|
||||
{
|
||||
/// <summary> Deletes files which are out of retainment scope. </summary>
|
||||
void DeleteObsoleteLogs();
|
||||
|
||||
/// <summary> Gets the log file name. </summary>
|
||||
string LogFileName { get; }
|
||||
|
||||
/// <summary> Gets all log files in logging directory. </summary>
|
||||
/// <returns>List of log files.</returns>
|
||||
IList<string> GetLogFiles();
|
||||
|
||||
/// <summary> Gets path where log files are located. </summary>
|
||||
/// <returns>Path to log files.</returns>
|
||||
string LogFilePath { get; }
|
||||
}
|
||||
}
|
35
TINKLib/Model/Logging/LogEntryClassifyHelper.cs
Normal file
35
TINKLib/Model/Logging/LogEntryClassifyHelper.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using Serilog;
|
||||
using System;
|
||||
using TINK.Model.Repository.Exception;
|
||||
|
||||
namespace TINK.Model.Logging
|
||||
{
|
||||
public static class LogEntryClassifyHelper
|
||||
{
|
||||
/// <summary> Classifies exception and logs information or error depending on result of classification. </summary>
|
||||
/// <typeparam name="T0">Type of first message parameter.</typeparam>
|
||||
/// <typeparam name="T1">Type of second message parameter.</typeparam>
|
||||
/// <param name="p_oLogger">Object to use for logging.</param>
|
||||
/// <param name="messageTemplate">Templated used to output message.</param>
|
||||
/// <param name="propertyValue0">First message parameter.</param>
|
||||
/// <param name="propertyValue1">Second message parameter.</param>
|
||||
/// <param name="p_oException">Exception to classify.</param>
|
||||
public static void InformationOrError<T0, T1>(this ILogger p_oLogger, string messageTemplate, T0 propertyValue0, T1 propertyValue1, Exception p_oException)
|
||||
{
|
||||
if (p_oException == null)
|
||||
{
|
||||
p_oLogger.Error(messageTemplate, propertyValue0, propertyValue1, string.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_oException.GetIsConnectFailureException())
|
||||
{
|
||||
// Expected exception (LAN or mobile data off/ not reachable, proxy, ...)
|
||||
p_oLogger.Information(messageTemplate, propertyValue0, propertyValue1, p_oException);
|
||||
return;
|
||||
}
|
||||
|
||||
p_oLogger.Error(messageTemplate, propertyValue0, propertyValue1, p_oException);
|
||||
}
|
||||
}
|
||||
}
|
103
TINKLib/Model/Logging/LoggerConfigurationHelper.cs
Normal file
103
TINKLib/Model/Logging/LoggerConfigurationHelper.cs
Normal file
|
@ -0,0 +1,103 @@
|
|||
using Serilog;
|
||||
using Serilog.Configuration;
|
||||
using Serilog.Formatting.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace TINK.Model.Logging
|
||||
{
|
||||
/// <summary> Holds new logging levels. </summary>
|
||||
public enum RollingInterval
|
||||
{
|
||||
/// <summary> Create a new log file for each session (start of app).</summary>
|
||||
Session,
|
||||
}
|
||||
|
||||
/// <summary> Provides logging file name helper functionality.</summary>
|
||||
public static class LoggerConfigurationHelper
|
||||
{
|
||||
/// <summary> Holds the log file name. </summary>
|
||||
private static ILoggingDirectoryManager m_oDirectoryManager = new EmptyDirectoryLoggingManger();
|
||||
|
||||
/// <summary> Sets up logging to file.</summary>
|
||||
/// <param name="p_oLoggerConfiguration">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>
|
||||
/// <returns>Logger object.</returns>
|
||||
public static LoggerConfiguration File(
|
||||
this LoggerSinkConfiguration p_oLoggerConfiguration,
|
||||
string p_strLogFileFolder,
|
||||
RollingInterval p_oRollingInterval = RollingInterval.Session,
|
||||
int p_iRetainedFilesCountLimit = 10)
|
||||
{
|
||||
if (m_oDirectoryManager is EmptyDirectoryLoggingManger)
|
||||
{
|
||||
// Roll file only once per app session.
|
||||
try
|
||||
{
|
||||
m_oDirectoryManager = new LoggingDirectoryManager(
|
||||
Directory.GetFiles,
|
||||
Directory.Exists,
|
||||
(path) => Directory.CreateDirectory(path),
|
||||
System.IO.File.Delete,
|
||||
p_strLogFileFolder,
|
||||
Path.DirectorySeparatorChar,
|
||||
p_iRetainedFilesCountLimit);
|
||||
}
|
||||
catch (Exception l_oException)
|
||||
{
|
||||
Log.Error("Log directory manager could not be instanciated successfully. {@l_oException}", l_oException);
|
||||
m_oDirectoryManager = new EmptyDirectoryLoggingManger();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
m_oDirectoryManager.DeleteObsoleteLogs();
|
||||
}
|
||||
catch (Exception l_oException)
|
||||
{
|
||||
Log.Error("Not all obsolte log files could be deleted successfully. {@l_oException}", l_oException);
|
||||
}
|
||||
|
||||
if (p_oLoggerConfiguration == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return p_oLoggerConfiguration.File(
|
||||
new JsonFormatter(),
|
||||
m_oDirectoryManager.LogFileName,
|
||||
/*shared: true, // Leads to exception if activated.*/
|
||||
rollingInterval: Serilog.RollingInterval.Infinite,
|
||||
retainedFileCountLimit: p_iRetainedFilesCountLimit);
|
||||
}
|
||||
|
||||
/// <summary> Gets all log files in logging directory. </summary>
|
||||
/// <param name="p_oLogger"></param>
|
||||
/// <returns>List of log files.</returns>
|
||||
public static IList<string> GetLogFiles(this ILogger p_oLogger)
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_oDirectoryManager.GetLogFiles();
|
||||
}
|
||||
catch (Exception l_oException)
|
||||
{
|
||||
Log.Error("Getting list of log files failed. Empty list is returned instead. {@l_oException}", l_oException);
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets path where log files are located. </summary>
|
||||
/// <param name="p_oLogger"></param>
|
||||
/// <returns>List of log files.</returns>
|
||||
public static string GetLogFilePath(
|
||||
this ILogger p_oLogger)
|
||||
{
|
||||
return m_oDirectoryManager.LogFilePath;
|
||||
}
|
||||
}
|
||||
}
|
147
TINKLib/Model/Logging/LoggingDirectoryManager.cs
Normal file
147
TINKLib/Model/Logging/LoggingDirectoryManager.cs
Normal file
|
@ -0,0 +1,147 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TINK.Model.Logging
|
||||
{
|
||||
public class LoggingDirectoryManager : ILoggingDirectoryManager
|
||||
{
|
||||
/// <summary> Name of logging subdirectory.</summary>
|
||||
private const string LOGDIRECTORYTITLE = "Log";
|
||||
|
||||
/// <summary> Prevents an invalid instance to be created. </summary>
|
||||
private LoggingDirectoryManager() { }
|
||||
|
||||
public LoggingDirectoryManager(
|
||||
Func<string, IList<string>> p_oFileListProvider,
|
||||
Func<string, bool> p_oDirectoryExistsChecker,
|
||||
Action<string> p_oDirectoryCreator,
|
||||
Action<string> p_oFileEraser,
|
||||
string p_oLogFilePath,
|
||||
char p_strDirectorySeparatorChar,
|
||||
int p_iRetainedFilesCountLimit)
|
||||
{
|
||||
m_oFileListProvider = p_oFileListProvider ?? throw new ArgumentException($"Can not instantiate {nameof(LoggingDirectoryManager)}- object. File list provider delegate can not be null.");
|
||||
|
||||
if (p_oDirectoryExistsChecker == null)
|
||||
{
|
||||
throw new ArgumentException($"Can not instantiate {nameof(LoggingDirectoryManager)}- object. Directory existance checker delegate can not be null.");
|
||||
}
|
||||
|
||||
if (p_oDirectoryCreator == null)
|
||||
{
|
||||
throw new ArgumentException($"Can not instantiate {nameof(LoggingDirectoryManager)}- object. Directory creator delegate can not be null.");
|
||||
}
|
||||
|
||||
m_oFileEraser = p_oFileEraser ?? throw new ArgumentException($"Can not instantiate {nameof(LoggingDirectoryManager)}- object. File eraser delegate can not be null.");
|
||||
|
||||
if (string.IsNullOrEmpty(p_oLogFilePath))
|
||||
{
|
||||
throw new ArgumentException($"Can not instantiate {nameof(LoggingDirectoryManager)}- object. Log file path can not be null or empty.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(p_strDirectorySeparatorChar.ToString()))
|
||||
{
|
||||
throw new ArgumentException($"Can not instantiate {nameof(LoggingDirectoryManager)}- object. Directory separtor character can not be null or empty.");
|
||||
}
|
||||
|
||||
if (p_iRetainedFilesCountLimit < 1)
|
||||
{
|
||||
throw new ArgumentException($"Can not instantiate {nameof(LoggingDirectoryManager)}- object. Count of retained log files is {p_iRetainedFilesCountLimit} but must be equal or larger one.");
|
||||
}
|
||||
|
||||
DirectorySeparatorChar = p_strDirectorySeparatorChar.ToString();
|
||||
|
||||
LogFilePath = $"{p_oLogFilePath}{DirectorySeparatorChar}{LOGDIRECTORYTITLE}";
|
||||
|
||||
|
||||
m_iRetainedFilesCountLimit = p_iRetainedFilesCountLimit;
|
||||
|
||||
if (string.IsNullOrEmpty(LogFileTitle))
|
||||
{
|
||||
LogFileTitle = $"{ DateTime.Now:yyyy_MM_dd_HH_mm_ss}.jsnl";
|
||||
}
|
||||
|
||||
// Create directory if direcotry does not exist.
|
||||
if (p_oDirectoryExistsChecker(LogFilePath) == false)
|
||||
{
|
||||
try
|
||||
{
|
||||
p_oDirectoryCreator(LogFilePath);
|
||||
}
|
||||
catch (Exception l_oException)
|
||||
{
|
||||
|
||||
throw new FileOperationException($"Logging directory {LogFilePath} could not be created successfully.", l_oException);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Deletes files which are out of retainment scope. </summary>
|
||||
public void DeleteObsoleteLogs()
|
||||
{
|
||||
var l_oExceptions = new List<Exception>();
|
||||
var l_oSortedFileArray = m_oFileListProvider(LogFilePath).OrderByDescending(x => x).ToArray();
|
||||
|
||||
for (int l_iIndex = l_oSortedFileArray.Length - 1;
|
||||
l_iIndex >= m_iRetainedFilesCountLimit - 1; /* files remaining count must be m_iRetainedFilesCountLimit - 1 because new log file will be added afterwards */
|
||||
l_iIndex --)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_oFileEraser(l_oSortedFileArray[l_iIndex]);
|
||||
}
|
||||
catch (Exception l_oExpetion)
|
||||
{
|
||||
// Getting list of log files found.
|
||||
l_oExceptions.Add(l_oExpetion);
|
||||
}
|
||||
}
|
||||
|
||||
if (l_oExceptions.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new AggregateException("Deleting obsolete log files failed.", l_oExceptions.ToArray());
|
||||
}
|
||||
|
||||
/// <summary> Gets all log files in logging directory. </summary>
|
||||
/// <param name="p_oLogger"></param>
|
||||
/// <returns>List of log files.</returns>
|
||||
public IList<string> GetLogFiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_oFileListProvider(LogFilePath).OrderBy(x => x).ToList();
|
||||
}
|
||||
catch (Exception l_oExpetion)
|
||||
{
|
||||
// Getting list of log files found.
|
||||
throw new FileOperationException("Getting list of log files failed.", l_oExpetion);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Holds delegate to provide file names.</summary>
|
||||
private readonly Func<string, IList<string>> m_oFileListProvider;
|
||||
|
||||
/// <summary>Holds delegate to delete files.</summary>
|
||||
private readonly Action<string> m_oFileEraser;
|
||||
|
||||
/// <summary>Holds delegate to provide file names.</summary>
|
||||
private int m_iRetainedFilesCountLimit;
|
||||
|
||||
/// <summary> Holds the log file name. </summary>
|
||||
private string LogFileTitle { get; }
|
||||
|
||||
/// <summary> Holds the log file name. </summary>
|
||||
public string LogFilePath { get; }
|
||||
|
||||
/// <summary> Holds the directory separator character. </summary>
|
||||
private string DirectorySeparatorChar { get; }
|
||||
|
||||
/// <summary> Holds the log file name. </summary>
|
||||
public string LogFileName { get { return $"{LogFilePath}{DirectorySeparatorChar}{LogFileTitle}"; } }
|
||||
}
|
||||
}
|
75
TINKLib/Model/Settings/GroupFilterSettings.cs
Normal file
75
TINKLib/Model/Settings/GroupFilterSettings.cs
Normal file
|
@ -0,0 +1,75 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TINK.Model;
|
||||
using TINK.Model.Connector.Filter;
|
||||
|
||||
namespace TINK.ViewModel.Settings
|
||||
{
|
||||
public class GroupFilterSettings : IGroupFilterSettings
|
||||
{
|
||||
|
||||
public GroupFilterSettings(IDictionary<string, FilterState> filterDictionary = null)
|
||||
{
|
||||
FilterDictionary = filterDictionary ?? new Dictionary<string, FilterState>();
|
||||
|
||||
Filter = filterDictionary != null
|
||||
? (IGroupFilter) new IntersectGroupFilter(FilterDictionary.Where(x => x.Value == FilterState.On).Select(x => x.Key))
|
||||
: new NullGroupFilter();
|
||||
}
|
||||
|
||||
private IDictionary<string, FilterState> FilterDictionary { get; set; }
|
||||
|
||||
private IGroupFilter Filter { get; }
|
||||
|
||||
/// <summary> Performs filtering on response-group. </summary>
|
||||
public IEnumerable<string> DoFilter(IEnumerable<string> filter = null) => Filter.DoFilter(filter);
|
||||
|
||||
public FilterState this[string key] { get => FilterDictionary[key]; set => FilterDictionary[key] = value; }
|
||||
|
||||
public ICollection<string> Keys => FilterDictionary.Keys;
|
||||
|
||||
public ICollection<FilterState> Values => FilterDictionary.Values;
|
||||
|
||||
public int Count => FilterDictionary.Count;
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
|
||||
public void Add(string key, FilterState value)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, FilterState> item)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<string, FilterState> item) => FilterDictionary.Contains(item);
|
||||
|
||||
public bool ContainsKey(string key) => FilterDictionary.ContainsKey(key);
|
||||
|
||||
public void CopyTo(KeyValuePair<string, FilterState>[] array, int arrayIndex) => FilterDictionary.CopyTo(array, arrayIndex);
|
||||
|
||||
public IEnumerator<KeyValuePair<string, FilterState>> GetEnumerator() => FilterDictionary.GetEnumerator();
|
||||
|
||||
public bool Remove(string key)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<string, FilterState> item)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out FilterState value) => FilterDictionary.TryGetValue(key, out value);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => FilterDictionary.GetEnumerator();
|
||||
}
|
||||
}
|
11
TINKLib/Model/Settings/IGroupFilterSettings.cs
Normal file
11
TINKLib/Model/Settings/IGroupFilterSettings.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
using TINK.Model;
|
||||
|
||||
namespace TINK.ViewModel.Settings
|
||||
{
|
||||
public interface IGroupFilterSettings : IDictionary<string, FilterState>
|
||||
{
|
||||
/// <summary> Performs filtering on response-group. </summary>
|
||||
IEnumerable<string> DoFilter(IEnumerable<string> filter = null);
|
||||
}
|
||||
}
|
515
TINKLib/Model/Settings/JsonSettingsDictionary.cs
Normal file
515
TINKLib/Model/Settings/JsonSettingsDictionary.cs
Normal file
|
@ -0,0 +1,515 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TINK.Model.Connector;
|
||||
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;
|
||||
|
||||
namespace TINK.Model.Settings
|
||||
{
|
||||
public static class JsonSettingsDictionary
|
||||
{
|
||||
/// <summary>Title of the settings file.</summary>
|
||||
public const string SETTINGSFILETITLE = "Setting.Json";
|
||||
|
||||
/// <summary> Key of the app version entry. </summary>
|
||||
public const string APPVERIONKEY = "AppVersion";
|
||||
|
||||
/// <summary> Key of the app version entry. </summary>
|
||||
public const string SHOWWHATSNEWKEY = "ShowWhatsNew";
|
||||
|
||||
/// <summary> Key of the app version entry. </summary>
|
||||
public const string EXPIRESAFTER = "ExpiresAfter";
|
||||
|
||||
/// <summary> Key of the connect timeout. </summary>
|
||||
public const string CONNECTTIMEOUT = "ConnectTimeout";
|
||||
|
||||
/// <summary> Key of the logging level entry. </summary>
|
||||
public const string MINLOGGINGLEVELKEY = "MinimumLoggingLevel";
|
||||
|
||||
/// <summary> Key of the center to ... entry. </summary>
|
||||
public const string CENTERMAPTOCURRENTLOCATION = "CenterMapToCurrentLocation";
|
||||
|
||||
public const string LOGTOEXTERNALFOLDER = "LogToExternalFolder";
|
||||
|
||||
public const string THEMEKEY = "Theme";
|
||||
|
||||
public const string ISSITECACHINGON = "IsSiteCachingOn";
|
||||
|
||||
/// <summary> Gets a nullable value.</summary>
|
||||
/// <param name="settingsJSON">Dictionary to get value from.</param>
|
||||
public static T? GetNullableEntry<T>(
|
||||
string keyName,
|
||||
Dictionary<string, string> settingsJSON) where T : struct
|
||||
{
|
||||
if (!settingsJSON.TryGetValue(keyName, out string boolText)
|
||||
|| string.IsNullOrEmpty(boolText))
|
||||
{
|
||||
// File holds no entry.
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(boolText);
|
||||
}
|
||||
|
||||
/// <summary> Gets a value to type class.</summary>
|
||||
/// <param name="settingsJSON">Dictionary to get value from.</param>
|
||||
public static T GetEntry<T>(
|
||||
string keyName,
|
||||
Dictionary<string, string> settingsJSON) where T : class
|
||||
{
|
||||
if (!settingsJSON.TryGetValue(keyName, out string boolText)
|
||||
|| string.IsNullOrEmpty(boolText))
|
||||
{
|
||||
// File holds no entry.
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(boolText);
|
||||
}
|
||||
|
||||
/// <summary> Sets a nullable.</summary>
|
||||
/// <param name="entry">Entry to add to dictionary.</param>
|
||||
/// <param name="keyName">Key to use for value. </param>
|
||||
/// <param name="targetDictionary">Dictionary to set value to.</param>
|
||||
public static Dictionary<string, string> SetEntry<T>(T entry, string keyName, IDictionary<string, string> targetDictionary)
|
||||
{
|
||||
// Set value.
|
||||
if (targetDictionary == null)
|
||||
throw new Exception($"Writing entry value {keyName} to dictionary failed. Dictionary must not be null.");
|
||||
|
||||
return targetDictionary.Union(new Dictionary<string, string>
|
||||
{
|
||||
{ keyName, JsonConvert.SerializeObject(entry) }
|
||||
}).ToDictionary(key => key.Key, value => value.Value);
|
||||
}
|
||||
|
||||
/// <summary> Sets the timeout to apply when connecting to bluetooth lock.</summary>
|
||||
/// <param name="targetDictionary">Dictionary to write information to.</param>
|
||||
/// <param name="connectTimeout">Connect timeout value.</param>
|
||||
public static Dictionary<string, string> SetConnectTimeout(
|
||||
this IDictionary<string, string> targetDictionary,
|
||||
TimeSpan connectTimeout)
|
||||
{
|
||||
if (targetDictionary == null)
|
||||
throw new Exception("Writing conntect timeout info failed. Dictionary must not be null.");
|
||||
|
||||
return targetDictionary.Union(new Dictionary<string, string>
|
||||
{
|
||||
{ CONNECTTIMEOUT, JsonConvert.SerializeObject(connectTimeout, new JavaScriptDateTimeConverter()) }
|
||||
}).ToDictionary(key => key.Key, value => value.Value);
|
||||
}
|
||||
/// <summary> Sets the uri of the active copri host. </summary>
|
||||
/// <param name="p_oSettingsJSON">Dictionary holding parameters from JSON.</param>
|
||||
public static Dictionary<string, string> SetCopriHostUri(this IDictionary<string, string> p_oTargetDictionary, string p_strNextActiveUriText)
|
||||
{
|
||||
if (p_oTargetDictionary == null)
|
||||
throw new Exception("Writing copri host uri to dictionary failed. Dictionary must not be null.");
|
||||
|
||||
return p_oTargetDictionary.Union(new Dictionary<string, string>
|
||||
{
|
||||
{ typeof(CopriServerUriList).ToString(), JsonConvert.SerializeObject(p_strNextActiveUriText) },
|
||||
}).ToDictionary(key => key.Key, value => value.Value);
|
||||
}
|
||||
|
||||
/// <summary> Gets the timeout to apply when connecting to bluetooth lock.</summary>
|
||||
/// <param name="p_oSettingsJSON">Dictionary to get information from.</param>
|
||||
/// <returns>Connect timeout value.</returns>
|
||||
public static TimeSpan? GetConnectTimeout(Dictionary<string, string> p_oSettingsJSON)
|
||||
{
|
||||
if (!p_oSettingsJSON.TryGetValue(CONNECTTIMEOUT, out string connectTimeout)
|
||||
|| string.IsNullOrEmpty(connectTimeout))
|
||||
{
|
||||
// File holds no entry.
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<TimeSpan>(connectTimeout, new JavaScriptDateTimeConverter());
|
||||
}
|
||||
/// <summary> Gets the logging level.</summary>
|
||||
/// <param name="settingsJSON">Dictionary to get logging level from.</param>
|
||||
/// <returns>Logging level</returns>
|
||||
public static Uri GetCopriHostUri(this IDictionary<string, string> settingsJSON)
|
||||
{
|
||||
// Get uri of corpi server.
|
||||
if (!settingsJSON.TryGetValue(typeof(CopriServerUriList).ToString(), out string uriText)
|
||||
|| string.IsNullOrEmpty(uriText))
|
||||
{
|
||||
// File holds no entry.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (uriText.ToUpper().ToUpper().Contains("copri-bike.de".ToUpper()))
|
||||
return new Uri(CopriServerUriList.SHAREE_DEVEL);
|
||||
|
||||
if (uriText.ToUpper().ToUpper().Contains("copri.eu".ToUpper()))
|
||||
return new Uri(CopriServerUriList.SHAREE_LIVE);
|
||||
|
||||
return JsonConvert.DeserializeObject<Uri>(uriText);
|
||||
}
|
||||
|
||||
/// <summary> Sets the version of the app. </summary>
|
||||
/// <param name="p_oSettingsJSON">Dictionary holding parameters from JSON.</param>
|
||||
public static Dictionary<string, string> SetAppVersion(this IDictionary<string, string> p_oTargetDictionary, Version p_strAppVersion)
|
||||
{
|
||||
if (p_oTargetDictionary == null)
|
||||
throw new Exception("Writing copri host uri to dictionary failed. Dictionary must not be null.");
|
||||
|
||||
return p_oTargetDictionary.Union(new Dictionary<string, string>
|
||||
{
|
||||
{APPVERIONKEY , JsonConvert.SerializeObject(p_strAppVersion, new VersionConverter()) },
|
||||
}).ToDictionary(key => key.Key, value => value.Value);
|
||||
}
|
||||
|
||||
/// <summary> Gets the app versions.</summary>
|
||||
/// <param name="p_oSettingsJSON">Dictionary to get logging level from.</param>
|
||||
/// <returns>Logging level</returns>
|
||||
public static Version GetAppVersion(this IDictionary<string, string> p_oSettingsJSON)
|
||||
{
|
||||
// Get the version of the app which wrote the settings file.
|
||||
if (!p_oSettingsJSON.TryGetValue(APPVERIONKEY, out string l_oAppVersion)
|
||||
|| string.IsNullOrEmpty(l_oAppVersion))
|
||||
{
|
||||
// File holds no entry.
|
||||
return null;
|
||||
}
|
||||
|
||||
return l_oAppVersion.TrimStart().StartsWith("\"")
|
||||
? JsonConvert.DeserializeObject<Version>(l_oAppVersion, new VersionConverter())
|
||||
: Version.Parse(l_oAppVersion); // Format used up to version 3.0.0.115
|
||||
}
|
||||
|
||||
|
||||
/// <summary> Sets whether polling is on or off and the periode if polling is on. </summary>
|
||||
/// <param name="p_oSettingsJSON">Dictionary to write entries to.</param>
|
||||
public static Dictionary<string, string> SetPollingParameters(this IDictionary<string, string> p_oTargetDictionary, PollingParameters p_oPollingParameter)
|
||||
{
|
||||
if (p_oTargetDictionary == null)
|
||||
throw new Exception("Writing polling parameters to dictionary failed. Dictionary must not be null.");
|
||||
|
||||
return p_oTargetDictionary.Union(new Dictionary<string, string>
|
||||
{
|
||||
{ $"{typeof(PollingParameters).Name}_{typeof(TimeSpan).Name}", JsonConvert.SerializeObject(p_oPollingParameter.Periode) },
|
||||
{ $"{typeof(PollingParameters).Name}_{typeof(bool).Name}", JsonConvert.SerializeObject(p_oPollingParameter.IsActivated) },
|
||||
}).ToDictionary(key => key.Key, value => value.Value);
|
||||
}
|
||||
|
||||
/// <summary> Get whether polling is on or off and the periode if polling is on. </summary>
|
||||
/// <param name="p_oSettingsJSON">Dictionary holding parameters from JSON.</param>
|
||||
/// <returns>Polling parameters.</returns>
|
||||
public static PollingParameters GetPollingParameters(this IDictionary<string, string> p_oSettingsJSON)
|
||||
{
|
||||
// Check if dictionary contains entry for periode.
|
||||
if (p_oSettingsJSON.TryGetValue($"{typeof(PollingParameters).Name}_{typeof(TimeSpan).Name}", out string l_strPeriode)
|
||||
&& p_oSettingsJSON.TryGetValue($"{typeof(PollingParameters).Name}_{typeof(bool).Name}", out string l_strIsActive)
|
||||
&& !string.IsNullOrEmpty(l_strPeriode)
|
||||
&& !string.IsNullOrEmpty(l_strIsActive))
|
||||
{
|
||||
return new PollingParameters(
|
||||
JsonConvert.DeserializeObject<TimeSpan>(l_strPeriode),
|
||||
JsonConvert.DeserializeObject<bool>(l_strIsActive));
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary> Saves object to file. </summary>
|
||||
/// <param name="p_SettingsList">Settings to save.</param>
|
||||
public static void Serialize(string p_strSettingsFileFolder, IDictionary<string, string> p_SettingsList)
|
||||
{
|
||||
// Save settings to file.
|
||||
var l_oText = JsonConvert.SerializeObject(p_SettingsList, Formatting.Indented);
|
||||
var l_oFolder = p_strSettingsFileFolder;
|
||||
System.IO.File.WriteAllText($"{l_oFolder}{System.IO.Path.DirectorySeparatorChar}{SETTINGSFILETITLE}", l_oText);
|
||||
}
|
||||
|
||||
/// <summary> Gets TINK app settings form xml- file. </summary>
|
||||
/// <param name="p_strSettingsDirectory">Directory to read settings from.</param>
|
||||
/// <returns>Dictionary of settings.</returns>
|
||||
public static Dictionary<string, string> Deserialize(string p_strSettingsDirectory)
|
||||
{
|
||||
var l_oFileName = $"{p_strSettingsDirectory}{System.IO.Path.DirectorySeparatorChar}{SETTINGSFILETITLE}";
|
||||
|
||||
if (!System.IO.File.Exists(l_oFileName))
|
||||
{
|
||||
// File is empty. Nothing to read.
|
||||
return new Dictionary<string, string>(); ;
|
||||
}
|
||||
|
||||
var l_oJSONFile = System.IO.File.ReadAllText(l_oFileName);
|
||||
|
||||
if (string.IsNullOrEmpty(l_oJSONFile))
|
||||
{
|
||||
// File is empty. Nothing to read.
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
// Load setting file.
|
||||
return JsonConvert.DeserializeObject<Dictionary<string, string>>(l_oJSONFile);
|
||||
}
|
||||
|
||||
/// <summary> Gets the logging level.</summary>
|
||||
/// <param name="p_oSettingsJSON">Dictionary to get logging level from.</param>
|
||||
/// <returns>Logging level.</returns>
|
||||
public static LogEventLevel? GetMinimumLoggingLevel(
|
||||
Dictionary<string, string> p_oSettingsJSON)
|
||||
{
|
||||
// Get logging level.
|
||||
if (!p_oSettingsJSON.TryGetValue(MINLOGGINGLEVELKEY, out string l_strLevel)
|
||||
|| string.IsNullOrEmpty(l_strLevel))
|
||||
{
|
||||
// File holds no entry.
|
||||
return null;
|
||||
}
|
||||
|
||||
return (LogEventLevel)int.Parse(JsonConvert.DeserializeObject<string>(l_strLevel));
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// Set logging level.
|
||||
if (p_oTargetDictionary == null)
|
||||
throw new Exception("Writing logging level to dictionary failed. Dictionary must not be null.");
|
||||
|
||||
return p_oTargetDictionary.Union(new Dictionary<string, string>
|
||||
{
|
||||
{ MINLOGGINGLEVELKEY, JsonConvert.SerializeObject((int)p_oLevel) }
|
||||
}).ToDictionary(key => key.Key, value => value.Value);
|
||||
}
|
||||
|
||||
/// <summary> Gets the version of app when whats new was shown.</summary>
|
||||
/// <param name="p_oSettingsJSON">Dictionary to get logging level from.</param>
|
||||
/// <returns>Version of the app.</returns>
|
||||
public static Version GetWhatsNew(Dictionary<string, string> p_oSettingsJSON)
|
||||
{
|
||||
// Get logging level.
|
||||
if (!p_oSettingsJSON.TryGetValue(SHOWWHATSNEWKEY, out string l_strWhatsNewVersion)
|
||||
|| string.IsNullOrEmpty(l_strWhatsNewVersion))
|
||||
{
|
||||
// File holds no entry.
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<Version>(l_strWhatsNewVersion, new VersionConverter());
|
||||
}
|
||||
|
||||
/// <summary> Sets the version of app when whats new was shown.</summary>
|
||||
/// <param name="p_oSettingsJSON">Dictionary to get information from.</param>
|
||||
public static Dictionary<string, string> SetWhatsNew(this IDictionary<string, string> p_oTargetDictionary, Version p_oAppVersion)
|
||||
{
|
||||
// Set logging level.
|
||||
if (p_oTargetDictionary == null)
|
||||
throw new Exception("Writing WhatsNew info failed. Dictionary must not be null.");
|
||||
|
||||
return p_oTargetDictionary.Union(new Dictionary<string, string>
|
||||
{
|
||||
{ SHOWWHATSNEWKEY, JsonConvert.SerializeObject(p_oAppVersion, new VersionConverter()) }
|
||||
}).ToDictionary(key => key.Key, value => value.Value);
|
||||
}
|
||||
|
||||
/// <summary> Gets the expires after value.</summary>
|
||||
/// <param name="p_oSettingsJSON">Dictionary to get expries after value from.</param>
|
||||
/// <returns>Expires after value.</returns>
|
||||
public static TimeSpan? GetExpiresAfter(Dictionary<string, string> p_oSettingsJSON)
|
||||
{
|
||||
if (!p_oSettingsJSON.TryGetValue(EXPIRESAFTER, out string expiresAfter)
|
||||
|| string.IsNullOrEmpty(expiresAfter))
|
||||
{
|
||||
// File holds no entry.
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<TimeSpan>(expiresAfter, new JavaScriptDateTimeConverter());
|
||||
}
|
||||
|
||||
/// <summary> Sets the the expiration time.</summary>
|
||||
/// <param name="p_oSettingsJSON">Dictionary to write information to.</param>
|
||||
public static Dictionary<string, string> SetExpiresAfter(this IDictionary<string, string> p_oTargetDictionary, TimeSpan expiresAfter)
|
||||
{
|
||||
if (p_oTargetDictionary == null)
|
||||
throw new Exception("Writing ExpiresAfter info failed. Dictionary must not be null.");
|
||||
|
||||
return p_oTargetDictionary.Union(new Dictionary<string, string>
|
||||
{
|
||||
{ EXPIRESAFTER, JsonConvert.SerializeObject(expiresAfter, new JavaScriptDateTimeConverter()) }
|
||||
}).ToDictionary(key => key.Key, value => value.Value);
|
||||
}
|
||||
|
||||
/// <summary> Sets the active lock service name. </summary>
|
||||
/// <param name="targetDictionary">Dictionary holding parameters from JSON.</param>
|
||||
public static Dictionary<string, string> SetActiveLockService(this IDictionary<string, string> targetDictionary, string activeLockService)
|
||||
{
|
||||
if (targetDictionary == null)
|
||||
throw new Exception("Writing active lock service name to dictionary failed. Dictionary must not be null.");
|
||||
|
||||
return targetDictionary.Union(new Dictionary<string, string>
|
||||
{
|
||||
{ typeof(ILocksService).Name, activeLockService },
|
||||
}).ToDictionary(key => key.Key, value => value.Value);
|
||||
}
|
||||
|
||||
/// <summary> Gets the active lock service name.</summary>
|
||||
/// <param name="settingsJSON">Dictionary to get logging level from.</param>
|
||||
/// <returns>Active lock service name.</returns>
|
||||
public static string GetActiveLockService(this IDictionary<string, string> settingsJSON)
|
||||
{
|
||||
// Get uri of corpi server.
|
||||
if (!settingsJSON.TryGetValue(typeof(ILocksService).Name, out string activeLockService)
|
||||
|| string.IsNullOrEmpty(activeLockService))
|
||||
{
|
||||
// File holds no entry.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (activeLockService == "TINK.Services.BluetoothLock.BLE.LockItByScanService")
|
||||
{
|
||||
// Name of this service was switched.
|
||||
return typeof(TINK.Services.BluetoothLock.BLE.LockItByScanServicePolling).FullName;
|
||||
}
|
||||
|
||||
return activeLockService;
|
||||
}
|
||||
|
||||
/// <summary> Sets the active Geolocation service name. </summary>
|
||||
/// <param name="targetDictionary">Dictionary holding parameters from JSON.</param>
|
||||
public static Dictionary<string, string> SetActiveGeolocationService(
|
||||
this IDictionary<string, string> targetDictionary,
|
||||
string activeGeolocationService)
|
||||
{
|
||||
if (targetDictionary == null)
|
||||
throw new Exception("Writing active geolocation service name to dictionary failed. Dictionary must not be null.");
|
||||
|
||||
return targetDictionary.Union(new Dictionary<string, string>
|
||||
{
|
||||
{ typeof(IGeolocation).Name, activeGeolocationService },
|
||||
}).ToDictionary(key => key.Key, value => value.Value);
|
||||
}
|
||||
|
||||
/// <summary> Gets the active Geolocation service name.</summary>
|
||||
/// <param name="settingsJSON">Dictionary to get name of geolocation service from.</param>
|
||||
/// <returns>Active lock service name.</returns>
|
||||
public static string GetActiveGeolocationService(this IDictionary<string, string> settingsJSON)
|
||||
{
|
||||
// Get uri of corpi server.
|
||||
if (!settingsJSON.TryGetValue(typeof(IGeolocation).Name, out string activeGeolocationService)
|
||||
|| string.IsNullOrEmpty(activeGeolocationService))
|
||||
{
|
||||
// File holds no entry.
|
||||
return null;
|
||||
}
|
||||
|
||||
return activeGeolocationService;
|
||||
}
|
||||
|
||||
/// <summary> Gets a value indicating whether to center the map to location or not.</summary>
|
||||
/// <param name="settingsJSON">Dictionary to get value from.</param>
|
||||
public static bool? GetCenterMapToCurrentLocation(Dictionary<string, string> settingsJSON) => GetNullableEntry<bool>(CENTERMAPTOCURRENTLOCATION, settingsJSON);
|
||||
|
||||
/// <summary> Sets a value indicating whether to center the map to location or not.</summary>
|
||||
/// <param name="p_oSettingsJSON">Dictionary to get value from.</param>
|
||||
public static Dictionary<string, string> SetCenterMapToCurrentLocation(this IDictionary<string, string> targetDictionary, bool centerMapToCurrentLocation)
|
||||
=> SetEntry(centerMapToCurrentLocation, CENTERMAPTOCURRENTLOCATION, targetDictionary);
|
||||
|
||||
/// <summary> Gets whether to store logging data on SD card or not.</summary>
|
||||
/// <param name="settingsJSON">Dictionary to get value from.</param>
|
||||
public static bool? GetLogToExternalFolder(Dictionary<string, string> settingsJSON) => GetNullableEntry<bool>(LOGTOEXTERNALFOLDER, settingsJSON);
|
||||
|
||||
/// <summary> Gets full class name of active theme.</summary>
|
||||
/// <param name="settingsJSON">Dictionary to get value from.</param>
|
||||
public static string GetActiveTheme(Dictionary<string, string> settingsJSON) => GetEntry<string>(THEMEKEY, settingsJSON);
|
||||
|
||||
/// <summary> Gets a value indicating whether site caching is on or off.</summary>
|
||||
/// <param name="settingsJSON">Dictionary to get value from.</param>
|
||||
public static bool? GetIsSiteCachingOn(Dictionary<string, string> settingsJSON) => GetNullableEntry<bool>(ISSITECACHINGON, settingsJSON);
|
||||
|
||||
/// <summary> Sets whether to store logging data on SD card or not.</summary>
|
||||
/// <param name="p_oSettingsJSON">Dictionary to get value from.</param>
|
||||
public static Dictionary<string, string> SetLogToExternalFolder(this IDictionary<string, string> targetDictionary, bool useSdCard) => SetEntry(useSdCard, LOGTOEXTERNALFOLDER, targetDictionary);
|
||||
|
||||
/// <summary> Sets active theme.</summary>
|
||||
/// <param name="targetDictionary">Dictionary to set value to.</param>
|
||||
public static Dictionary<string, string> SetActiveTheme(this IDictionary<string, string> targetDictionary, string theme) => SetEntry(theme, THEMEKEY, targetDictionary);
|
||||
|
||||
/// <summary> Sets whether site caching is on or off.</summary>
|
||||
/// <param name="p_oSettingsJSON">Dictionary to get value from.</param>
|
||||
public static Dictionary<string, string> SetIsSiteCachingOn(this IDictionary<string, string> targetDictionary, bool useSdCard) => SetEntry(useSdCard, ISSITECACHINGON, targetDictionary);
|
||||
|
||||
/// <summary> Gets the map page filter. </summary>
|
||||
/// <param name="settings">Settings objet to load from.</param>
|
||||
public static IGroupFilterMapPage GetGroupFilterMapPage(this IDictionary<string, string> settings)
|
||||
{
|
||||
var l_oKeyName = "FilterCollection_MapPageFilter";
|
||||
if (settings == null || !settings.ContainsKey(l_oKeyName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new GroupFilterMapPage(JsonConvert.DeserializeObject<IDictionary<string, FilterState>>(settings[l_oKeyName]));
|
||||
}
|
||||
|
||||
public static IDictionary<string, string> SetGroupFilterMapPage(
|
||||
this IDictionary<string, string> settings,
|
||||
IDictionary<string, FilterState> p_oFilterCollection)
|
||||
{
|
||||
if (settings == null
|
||||
|| p_oFilterCollection == null
|
||||
|| p_oFilterCollection.Count < 1)
|
||||
{
|
||||
return settings;
|
||||
}
|
||||
|
||||
settings["FilterCollection_MapPageFilter"] = JsonConvert.SerializeObject(p_oFilterCollection);
|
||||
return settings;
|
||||
}
|
||||
|
||||
/// <summary> Gets the settings filter. </summary>
|
||||
/// <param name="settings">Settings objet to load from.</param>
|
||||
public static IGroupFilterSettings GetGoupFilterSettings(this IDictionary<string, string> settings)
|
||||
{
|
||||
var l_oKeyName = "FilterCollection";
|
||||
if (settings == null || !settings.ContainsKey(l_oKeyName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var legacyFilterCollection = new GroupFilterSettings(JsonConvert.DeserializeObject<IDictionary<string, FilterState>>(settings[l_oKeyName]));
|
||||
|
||||
// Process legacy entries.
|
||||
var updatedFilterCollection = legacyFilterCollection.Where(x => x.Key.ToUpper() != "TINK.SMS" && x.Key.ToUpper() != "TINK.COPRI").ToDictionary(x => x.Key, x => x.Value);
|
||||
if (legacyFilterCollection.Count() <= updatedFilterCollection.Count())
|
||||
return legacyFilterCollection;
|
||||
var list = updatedFilterCollection.ToList();
|
||||
|
||||
updatedFilterCollection.Add(
|
||||
FilterHelper.FILTERTINKGENERAL,
|
||||
legacyFilterCollection.Any(x => x.Key.ToUpper() == "TINK.COPRI") ? legacyFilterCollection.FirstOrDefault(x => x.Key.ToUpper() == "TINK.COPRI").Value : FilterState.Off);
|
||||
|
||||
return new GroupFilterSettings(updatedFilterCollection);
|
||||
}
|
||||
|
||||
public static IDictionary<string, string> SetGroupFilterSettings(
|
||||
this IDictionary<string, string> settings,
|
||||
IDictionary<string, FilterState> p_oFilterCollection)
|
||||
{
|
||||
if (settings == null
|
||||
|| p_oFilterCollection == null
|
||||
|| p_oFilterCollection.Count < 1)
|
||||
{
|
||||
return settings;
|
||||
}
|
||||
|
||||
settings["FilterCollection"] = JsonConvert.SerializeObject(p_oFilterCollection);
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
}
|
100
TINKLib/Model/Settings/PollingParameters.cs
Normal file
100
TINKLib/Model/Settings/PollingParameters.cs
Normal file
|
@ -0,0 +1,100 @@
|
|||
using System;
|
||||
|
||||
namespace TINK.Settings
|
||||
{
|
||||
/// <summary> Holds polling parameters.</summary>
|
||||
public sealed class PollingParameters : IEquatable<PollingParameters>
|
||||
{
|
||||
/// <summary> Holds default polling parameters. </summary>
|
||||
public static PollingParameters Default { get; } = new PollingParameters(
|
||||
new TimeSpan(0, 0, 0, 10 /*secs*/, 0),// Default polling interval.
|
||||
true);
|
||||
|
||||
/// <summary> Holds polling parameters which represent polling off (empty polling object). </summary>
|
||||
public static PollingParameters NoPolling { get; } = new PollingParameters(
|
||||
TimeSpan.MaxValue,// Very long intervall which should never be used because polling IsActivated property is set to false.
|
||||
false);
|
||||
|
||||
/// <summary> Constructs a polling parameter object. </summary>
|
||||
/// <param name="p_oPeriode">Polling periode.</param>
|
||||
/// <param name="p_bIsActivated">True if polling is activated.</param>
|
||||
public PollingParameters(TimeSpan p_oPeriode, bool p_bIsActivated)
|
||||
{
|
||||
Periode = p_oPeriode; // Can not be null because is a struct.
|
||||
IsActivated = p_bIsActivated;
|
||||
}
|
||||
|
||||
/// <summary>Holds the polling periode.</summary>
|
||||
public TimeSpan Periode { get; }
|
||||
|
||||
/// <summary> Holds value whether polling is activated or not.</summary>
|
||||
public bool IsActivated { get; }
|
||||
|
||||
/// <summary> Checks equallity.</summary>
|
||||
/// <param name="other">Object to compare with.</param>
|
||||
/// <returns>True if objects are equal.</returns>
|
||||
public bool Equals(PollingParameters other)
|
||||
{
|
||||
return this == other;
|
||||
}
|
||||
|
||||
/// <summary> Checks equallity.</summary>
|
||||
/// <param name="obj">Object to compare with.</param>
|
||||
/// <returns>True if objects are equal.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var l_oParameters = obj as PollingParameters;
|
||||
if (l_oParameters == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return this == l_oParameters;
|
||||
}
|
||||
|
||||
/// <summary> Gets the has code of object.</summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Periode.GetHashCode() ^ IsActivated.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary> Gets the string representation of the object.</summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Polling is on={IsActivated}, polling interval={Periode.TotalSeconds}[sec].";
|
||||
}
|
||||
|
||||
/// <summary>Defines equality of thwo polling parameter objects.</summary>
|
||||
/// <param name="p_oSource">First object to compare.</param>
|
||||
/// <param name="p_oTarget">Second object to compare.</param>
|
||||
/// <returns>True if objects are equal</returns>
|
||||
public static bool operator ==(PollingParameters p_oSource, PollingParameters p_oTarget)
|
||||
{
|
||||
if (p_oSource is null && p_oTarget is null)
|
||||
{
|
||||
// Both object are null
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_oSource is null ^ p_oTarget is null)
|
||||
{
|
||||
// Only one object is null.
|
||||
return false;
|
||||
}
|
||||
|
||||
return p_oSource.Periode == p_oTarget.Periode
|
||||
&& p_oSource.IsActivated == p_oTarget.IsActivated;
|
||||
}
|
||||
|
||||
/// <summary>Defines equality of thwo polling parameter objects.</summary>
|
||||
/// <param name="p_oSource">First object to compare.</param>
|
||||
/// <param name="p_oTarget">Second object to compare.</param>
|
||||
/// <returns>True if objects are equal</returns>
|
||||
public static bool operator !=(PollingParameters p_oSource, PollingParameters p_oTarget)
|
||||
{
|
||||
return (p_oSource == p_oTarget) == false;
|
||||
}
|
||||
}
|
||||
}
|
104
TINKLib/Model/Settings/Settings.cs
Normal file
104
TINKLib/Model/Settings/Settings.cs
Normal file
|
@ -0,0 +1,104 @@
|
|||
using Serilog.Events;
|
||||
using System;
|
||||
using TINK.Model.Services.Geolocation;
|
||||
using TINK.Services.BluetoothLock;
|
||||
using TINK.Services.CopriApi.ServerUris;
|
||||
using TINK.Settings;
|
||||
using TINK.ViewModel.Map;
|
||||
using TINK.ViewModel.Settings;
|
||||
|
||||
|
||||
namespace TINK.Model.Settings
|
||||
{
|
||||
/// <summary> Holds settings which are persisted.</summary>
|
||||
public class Settings
|
||||
{
|
||||
public const LogEventLevel DEFAULTLOGGINLEVEL = LogEventLevel.Error;
|
||||
|
||||
// Default value of the expires after entry. Controls the expiration time of the cache values.
|
||||
private TimeSpan DEFAULTEXPIRESAFTER = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary> Constructs settings object. </summary>
|
||||
/// <param name="groupFilterMapPage">filter which is applied on the map view. Either TINK or Konrad stations are displayed.</param>
|
||||
/// <param name="groupFilterSettings"></param>
|
||||
/// <param name="activeUri"></param>
|
||||
/// <param name="pollingParameters"></param>
|
||||
/// <param name="minimumLogEventLevel">Minimum logging level to be applied.</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>
|
||||
/// <param name="activeTheme">Full class name of active app theme.</param>
|
||||
public Settings(
|
||||
IGroupFilterMapPage groupFilterMapPage = null,
|
||||
IGroupFilterSettings groupFilterSettings = null,
|
||||
Uri activeUri = null,
|
||||
PollingParameters pollingParameters = null,
|
||||
LogEventLevel? minimumLogEventLevel = null,
|
||||
TimeSpan? expiresAfter = null,
|
||||
string activeLockService = null,
|
||||
TimeSpan? connectTimeout = null,
|
||||
string activeGeolocationService = null,
|
||||
bool? centerMapToCurrentLocation = null,
|
||||
bool? logToExternalFolder = null,
|
||||
bool? isSiteCachingOn = null,
|
||||
string activeTheme = null)
|
||||
{
|
||||
GroupFilterMapPage = groupFilterMapPage ?? GroupFilterHelper.GetMapPageFilterDefaults;
|
||||
GroupFilterSettings = groupFilterSettings ?? GroupFilterHelper.GetSettingsFilterDefaults;
|
||||
ActiveUri = GetActiveUri(activeUri);
|
||||
PollingParameters = pollingParameters ?? PollingParameters.Default;
|
||||
MinimumLogEventLevel = minimumLogEventLevel ?? DEFAULTLOGGINLEVEL;
|
||||
ExpiresAfter = expiresAfter ?? DEFAULTEXPIRESAFTER;
|
||||
ActiveLockService = activeLockService ?? LocksServicesContainerMutable.DefaultLocksservice;
|
||||
ConnectTimeout = connectTimeout ?? new TimeSpan(0, 0, TimeOutProvider.DEFAULT_BLUETOOTHCONNECT_TIMEOUTSECONDS); // Try one sec. to connect.
|
||||
ActiveGeolocationService = activeGeolocationService ?? typeof(LastKnownGeolocationService).Name;
|
||||
CenterMapToCurrentLocation = centerMapToCurrentLocation ?? GetCenterMapToCurrentLocation(activeUri);
|
||||
LogToExternalFolder = logToExternalFolder ?? false;
|
||||
IsSiteCachingOn = isSiteCachingOn ?? true;
|
||||
ActiveTheme = activeTheme ?? typeof(Themes.ShareeBike).FullName;
|
||||
}
|
||||
|
||||
/// <summary> Holds the filter which is applied on the map view. Either TINK or Konrad stations are displayed. </summary>
|
||||
public IGroupFilterMapPage GroupFilterMapPage { get; }
|
||||
|
||||
/// <summary> Holds the filters loaded from settings. </summary>
|
||||
public IGroupFilterSettings GroupFilterSettings { get; }
|
||||
|
||||
/// <summary> Holds the uri to connect to. </summary>
|
||||
public Uri ActiveUri { get; }
|
||||
|
||||
/// <summary> Holds the polling parameters. </summary>
|
||||
public PollingParameters PollingParameters { get; }
|
||||
|
||||
/// <summary> Gets the minimum logging level. </summary>
|
||||
public LogEventLevel MinimumLogEventLevel { get; }
|
||||
|
||||
/// <summary> Gets the expires after value.</summary>
|
||||
public TimeSpan ExpiresAfter { get; }
|
||||
|
||||
/// <summary> Gets the lock service to use.</summary>
|
||||
public string ActiveLockService { get; private set; }
|
||||
|
||||
/// <summary> Timeout to apply when connecting to bluetooth lock.</summary>
|
||||
public TimeSpan ConnectTimeout { get; }
|
||||
|
||||
/// <summary> Gets the geolocation service to use.</summary>
|
||||
public string ActiveGeolocationService { get; }
|
||||
|
||||
public bool CenterMapToCurrentLocation { get; }
|
||||
|
||||
public bool LogToExternalFolder { get; }
|
||||
|
||||
public bool IsSiteCachingOn { get; }
|
||||
|
||||
public string ActiveTheme { get; }
|
||||
|
||||
public static Uri GetActiveUri(Uri activeUri) => activeUri ?? Services.CopriApi.ServerUris.CopriServerUriList.DefaultActiveUri;
|
||||
|
||||
public static bool GetCenterMapToCurrentLocation(Uri activeUri)
|
||||
{
|
||||
// TINK does not require acess to current location. Deactivate center map to current location for this reason.
|
||||
return !GetActiveUri(activeUri).Host.GetIsCopri();
|
||||
}
|
||||
}
|
||||
}
|
20
TINKLib/Model/State/BaseState.cs
Normal file
20
TINKLib/Model/State/BaseState.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace TINK.Model.State
|
||||
{
|
||||
/// <summary>
|
||||
/// Base type for serialization purposes.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[KnownType(typeof(StateAvailableInfo))]
|
||||
[KnownType(typeof(StateRequestedInfo))]
|
||||
[KnownType(typeof(StateOccupiedInfo))]
|
||||
public abstract class BaseState
|
||||
{
|
||||
/// <summary> Constructor for Json serialization. </summary>
|
||||
/// <param name="p_eValue">State value.</param>
|
||||
protected BaseState(InUseStateEnum p_eValue) {}
|
||||
|
||||
public abstract InUseStateEnum Value { get; }
|
||||
}
|
||||
}
|
11
TINKLib/Model/State/IBaseState.cs
Normal file
11
TINKLib/Model/State/IBaseState.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
|
||||
namespace TINK.Model.State
|
||||
{
|
||||
/// <summary>
|
||||
/// Base state information.
|
||||
/// </summary>
|
||||
public interface IBaseState
|
||||
{
|
||||
InUseStateEnum Value { get; }
|
||||
}
|
||||
}
|
14
TINKLib/Model/State/INotAvailableState.cs
Normal file
14
TINKLib/Model/State/INotAvailableState.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
|
||||
namespace TINK.Model.State
|
||||
{
|
||||
/// <summary>
|
||||
/// State of bikes which are either reserved or booked.
|
||||
/// </summary>
|
||||
public interface INotAvailableState : IBaseState
|
||||
{
|
||||
DateTime From { get; }
|
||||
string MailAddress { get; }
|
||||
string Code { get; }
|
||||
}
|
||||
}
|
16
TINKLib/Model/State/IStateInfo.cs
Normal file
16
TINKLib/Model/State/IStateInfo.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
|
||||
namespace TINK.Model.State
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface to access informations about bike information.
|
||||
/// </summary>
|
||||
public interface IStateInfo : IBaseState
|
||||
{
|
||||
string MailAddress { get; }
|
||||
|
||||
DateTime? From { get; }
|
||||
|
||||
string Code { get; }
|
||||
}
|
||||
}
|
23
TINKLib/Model/State/IStateInfoMutable.cs
Normal file
23
TINKLib/Model/State/IStateInfoMutable.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace TINK.Model.State
|
||||
{
|
||||
public interface IStateInfoMutable
|
||||
{
|
||||
InUseStateEnum Value { get; }
|
||||
|
||||
/// <summary> Updates state from webserver. </summary>
|
||||
/// <param name="p_oState">State of the bike.</param>
|
||||
/// <param name="p_oFrom">Date time when bike was reserved/ booked.</param>
|
||||
/// <param name="p_oDuration">Lenght of time span for which bike remains booked.</param>
|
||||
/// <param name="p_strMailAddress">Mailaddress of the one which reserved/ booked.</param>
|
||||
/// <param name="p_strCode">Booking code if bike is booked or reserved.</param>
|
||||
/// <param name="notifyLevel">Controls whether notify property changed events are fired or not.</param>
|
||||
void Load(
|
||||
InUseStateEnum p_oState,
|
||||
DateTime? p_oFrom = null,
|
||||
string p_strMailAddress = null,
|
||||
string p_strCode = null,
|
||||
Bikes.Bike.BC.NotifyPropertyChangedLevel notifyLevel = Bikes.Bike.BC.NotifyPropertyChangedLevel.All);
|
||||
}
|
||||
}
|
39
TINKLib/Model/State/StateAvailableInfo.cs
Normal file
39
TINKLib/Model/State/StateAvailableInfo.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace TINK.Model.State
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the state available.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public sealed class StateAvailableInfo : BaseState, IBaseState
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs state info object representing state available.
|
||||
/// </summary>
|
||||
public StateAvailableInfo() : base(InUseStateEnum.Disposable)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary> Constructor for Json serialization. </summary>
|
||||
/// <param name="p_eValue">Unused value.</param>
|
||||
[JsonConstructor]
|
||||
private StateAvailableInfo (InUseStateEnum p_eValue) : base(InUseStateEnum.Disposable)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the info that state is disposable.
|
||||
/// Setter exists only for serialization purposes.
|
||||
/// </summary>
|
||||
public override InUseStateEnum Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return InUseStateEnum.Disposable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
161
TINKLib/Model/State/StateInfo.cs
Normal file
161
TINKLib/Model/State/StateInfo.cs
Normal file
|
@ -0,0 +1,161 @@
|
|||
using System;
|
||||
|
||||
namespace TINK.Model.State
|
||||
{
|
||||
/// <summary>
|
||||
/// Types of rent states
|
||||
/// </summary>
|
||||
public enum InUseStateEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// Bike is not in use. Corresponding COPRI state is "available".
|
||||
/// </summary>
|
||||
Disposable,
|
||||
|
||||
/// <summary>
|
||||
/// Bike is reserved. Corresponding COPRI state is "requested".
|
||||
/// </summary>
|
||||
Reserved,
|
||||
|
||||
/// <summary>
|
||||
/// Bike is booked. Corresponding COPRI statie is "occupied".
|
||||
/// </summary>
|
||||
Booked
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages the state of a bike.
|
||||
/// </summary>
|
||||
public class StateInfo : IStateInfo
|
||||
{
|
||||
// Holds the current disposable state value
|
||||
private readonly BaseState m_oInUseState;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a state info object when state is available.
|
||||
/// </summary>
|
||||
/// <param name="p_oDateTimeNowProvider">Provider for current date time to calculate remainig time on demand for state of type reserved.</param>
|
||||
public StateInfo()
|
||||
{
|
||||
m_oInUseState = new StateAvailableInfo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a state info object when state is requested.
|
||||
/// </summary>
|
||||
/// <param name="p_oRequestedAt">Date time when bike was requested</param>
|
||||
/// <param name="p_strMailAddress">Mail address of user which requested bike.</param>
|
||||
/// <param name="p_strCode">Booking code.</param>
|
||||
/// <param name="p_oDateTimeNowProvider">Date time provider to calculate reaining time.</param>
|
||||
public StateInfo(
|
||||
Func<DateTime> p_oDateTimeNowProvider,
|
||||
DateTime p_oRequestedAt,
|
||||
string p_strMailAddress,
|
||||
string p_strCode)
|
||||
{
|
||||
// Todo: Handle p_oFrom == null here.
|
||||
// Todo: Handle p_oDuration == null here.
|
||||
m_oInUseState = new StateRequestedInfo(
|
||||
p_oDateTimeNowProvider ?? (() => DateTime.Now),
|
||||
p_oRequestedAt,
|
||||
p_strMailAddress,
|
||||
p_strCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a state info object when state is booked.
|
||||
/// </summary>
|
||||
/// <param name="p_oBookedAt">Date time when bike was booked</param>
|
||||
/// <param name="p_strMailAddress">Mail address of user which booked bike.</param>
|
||||
/// <param name="p_strCode">Booking code.</param>
|
||||
public StateInfo(
|
||||
DateTime p_oBookedAt,
|
||||
string p_strMailAddress,
|
||||
string p_strCode)
|
||||
{
|
||||
// Todo: Handle p_oFrom == null here.
|
||||
// Todo: Clearify question: What to do if code changes form one value to another? This should never happen.
|
||||
// Todo: Clearify question: What to do if from time changes form one value to another? This should never happen.
|
||||
m_oInUseState = new StateOccupiedInfo(
|
||||
p_oBookedAt,
|
||||
p_strMailAddress,
|
||||
p_strCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state value of object.
|
||||
/// </summary>
|
||||
public InUseStateEnum Value
|
||||
{
|
||||
get { return m_oInUseState.Value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Member for serialization purposes.
|
||||
/// </summary>
|
||||
internal BaseState StateInfoObject
|
||||
{
|
||||
get { return m_oInUseState; }
|
||||
}
|
||||
/// Transforms object to string.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public new string ToString()
|
||||
{
|
||||
return m_oInUseState.Value.ToString("g");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Date of request/ bookeing action.
|
||||
/// </summary>
|
||||
public DateTime? From
|
||||
{
|
||||
get
|
||||
{
|
||||
var l_oNotDisposableInfo = m_oInUseState as INotAvailableState;
|
||||
return l_oNotDisposableInfo != null ? l_oNotDisposableInfo.From : (DateTime?)null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mail address.
|
||||
/// </summary>
|
||||
public string MailAddress
|
||||
{
|
||||
get
|
||||
{
|
||||
var l_oNotDisposableInfo = m_oInUseState as INotAvailableState;
|
||||
return l_oNotDisposableInfo?.MailAddress;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reservation code.
|
||||
/// </summary>
|
||||
public string Code
|
||||
{
|
||||
get
|
||||
{
|
||||
var l_oNotDisposableInfo = m_oInUseState as INotAvailableState;
|
||||
return l_oNotDisposableInfo?.Code;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries update
|
||||
/// </summary>
|
||||
/// <returns>True if reservation span has not exeeded and state remains reserved, false otherwise.</returns>
|
||||
/// <todo>Implement logging of time stamps.</todo>
|
||||
public bool GetIsStillReserved(out TimeSpan? p_oRemainingTime)
|
||||
{
|
||||
var l_oReservedInfo = m_oInUseState as StateRequestedInfo;
|
||||
if (l_oReservedInfo == null)
|
||||
{
|
||||
p_oRemainingTime = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return l_oReservedInfo.GetIsStillReserved(out p_oRemainingTime);
|
||||
}
|
||||
}
|
||||
}
|
299
TINKLib/Model/State/StateInfoMutable.cs
Normal file
299
TINKLib/Model/State/StateInfoMutable.cs
Normal file
|
@ -0,0 +1,299 @@
|
|||
using System;
|
||||
|
||||
namespace TINK.Model.State
|
||||
{
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Manges the state of a bike.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class StateInfoMutable : INotifyPropertyChanged, IStateInfoMutable
|
||||
{
|
||||
/// <summary>
|
||||
/// Provider for current date time to calculate remainig time on demand for state of type reserved.
|
||||
/// </summary>
|
||||
private readonly Func<DateTime> m_oDateTimeNowProvider;
|
||||
|
||||
// Holds the current disposable state value
|
||||
private StateInfo m_oStateInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Backs up remaining time of child object.
|
||||
/// </summary>
|
||||
private TimeSpan? m_oRemainingTime = null;
|
||||
|
||||
/// <summary> Notifies clients about state changes. </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a state object from source.
|
||||
/// </summary>
|
||||
/// <param name="p_oState">State info to load from.</param>
|
||||
public StateInfoMutable(
|
||||
Func<DateTime> p_oDateTimeNowProvider = null,
|
||||
IStateInfo p_oState = null)
|
||||
{
|
||||
// Back up date time provider to be able to pass this to requested- object if state changes to requested.
|
||||
m_oDateTimeNowProvider = p_oDateTimeNowProvider != null
|
||||
? p_oDateTimeNowProvider
|
||||
: () => DateTime.Now;
|
||||
|
||||
m_oStateInfo = Create(p_oState, p_oDateTimeNowProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads state from immutable source.
|
||||
/// </summary>
|
||||
/// <param name="p_oState">State to load from.</param>
|
||||
public void Load(IStateInfo p_oState)
|
||||
{
|
||||
if (p_oState == null)
|
||||
{
|
||||
throw new ArgumentException("Can not load state info, object must not be null.");
|
||||
}
|
||||
|
||||
// Back up last state value and remaining time value
|
||||
// to be able to check whether an event has to be fired or not.
|
||||
var l_oLastState = Value;
|
||||
var l_oLastRemainingTime = m_oRemainingTime;
|
||||
|
||||
// Create new state info object from source.
|
||||
m_oStateInfo = Create(p_oState, m_oDateTimeNowProvider);
|
||||
|
||||
// Update remaining time value.
|
||||
m_oStateInfo.GetIsStillReserved(out m_oRemainingTime);
|
||||
|
||||
if (l_oLastState == m_oStateInfo.Value
|
||||
&& l_oLastRemainingTime == m_oRemainingTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// State has changed, notify clients.
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(State)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a state info object.
|
||||
/// </summary>
|
||||
/// <param name="p_oState">State to load from.</param>
|
||||
private static StateInfo Create(
|
||||
IStateInfo p_oState,
|
||||
Func<DateTime> p_oDateTimeNowProvider)
|
||||
{
|
||||
switch (p_oState != null ? p_oState.Value : InUseStateEnum.Disposable)
|
||||
{
|
||||
case InUseStateEnum.Disposable:
|
||||
return new StateInfo();
|
||||
|
||||
case InUseStateEnum.Reserved:
|
||||
// Todo: Handle p_oFrom == null here.
|
||||
// Todo: Handle p_oDuration == null here.
|
||||
return new StateInfo(
|
||||
p_oDateTimeNowProvider,
|
||||
p_oState.From.Value,
|
||||
p_oState.MailAddress,
|
||||
p_oState.Code);
|
||||
|
||||
|
||||
case InUseStateEnum.Booked:
|
||||
// Todo: Handle p_oFrom == null here.
|
||||
// Todo: Clearify question: What to do if code changes form one value to another? This should never happen.
|
||||
// Todo: Clearify question: What to do if from time changes form one value to another? This should never happen.
|
||||
return new StateInfo(
|
||||
p_oState.From.Value,
|
||||
p_oState.MailAddress,
|
||||
p_oState.Code);
|
||||
|
||||
default:
|
||||
// Todo: New state Busy has to be defined.
|
||||
throw new Exception(string.Format("Can not create new state info object. Unknown state {0} detected.", p_oState.Value));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state value of object.
|
||||
/// </summary>
|
||||
public InUseStateEnum Value
|
||||
{
|
||||
get { return m_oStateInfo.Value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Member for serialization purposes.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
private BaseState StateInfoObject
|
||||
{
|
||||
get { return m_oStateInfo.StateInfoObject; }
|
||||
set
|
||||
{
|
||||
var l_oStateOccupied = value as StateOccupiedInfo;
|
||||
if (l_oStateOccupied != null)
|
||||
{
|
||||
m_oStateInfo = new StateInfo(l_oStateOccupied.From, l_oStateOccupied.MailAddress, l_oStateOccupied.Code);
|
||||
return;
|
||||
}
|
||||
|
||||
var l_oStateRequested = value as StateRequestedInfo;
|
||||
if (l_oStateRequested != null)
|
||||
{
|
||||
m_oStateInfo = new StateInfo(m_oDateTimeNowProvider, l_oStateRequested.From, l_oStateRequested.MailAddress, l_oStateRequested.Code);
|
||||
return;
|
||||
}
|
||||
|
||||
m_oStateInfo = new StateInfo();
|
||||
|
||||
}
|
||||
}
|
||||
/// Transforms object to string.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public new string ToString()
|
||||
{
|
||||
return m_oStateInfo.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks and updates state if required.
|
||||
/// </summary>
|
||||
/// <returns>Value indicating wheter state has changed</returns>
|
||||
public void UpdateOnTimeElapsed()
|
||||
{
|
||||
switch (m_oStateInfo.Value )
|
||||
{
|
||||
// State is disposable or booked. No need to update "OnTimeElapsed"
|
||||
case InUseStateEnum.Disposable:
|
||||
case InUseStateEnum.Booked:
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if maximum reserved time has elapsed.
|
||||
if (!m_oStateInfo.GetIsStillReserved(out m_oRemainingTime))
|
||||
{
|
||||
// Time has elapsed, switch state to disposable and notfiy client
|
||||
m_oStateInfo = new StateInfo();
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(State)));
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RemainingTime)));
|
||||
}
|
||||
|
||||
/// <summary> Updates state from webserver. </summary>
|
||||
/// <param name="p_oState">State of the bike.</param>
|
||||
/// <param name="p_oFrom">Date time when bike was reserved/ booked.</param>
|
||||
/// <param name="p_oDuration">Lenght of time span for which bike remains booked.</param>
|
||||
/// <param name="p_strMailAddress">Mailaddress of the one which reserved/ booked.</param>
|
||||
/// <param name="p_strCode">Booking code if bike is booked or reserved.</param>
|
||||
/// <param name="supressNotifyPropertyChanged">Controls whether notify property changed events are fired or not.</param>
|
||||
public void Load(
|
||||
InUseStateEnum p_oState,
|
||||
DateTime? p_oFrom = null,
|
||||
string p_strMailAddress = null,
|
||||
string p_strCode = null,
|
||||
Bikes.Bike.BC.NotifyPropertyChangedLevel notifyLevel = Bikes.Bike.BC.NotifyPropertyChangedLevel.All)
|
||||
{
|
||||
var l_oLastState = m_oStateInfo.Value;
|
||||
|
||||
switch (p_oState)
|
||||
{
|
||||
case InUseStateEnum.Disposable:
|
||||
m_oStateInfo = new StateInfo();
|
||||
|
||||
// Set value to null. Otherwise potentially obsolete value will be taken remaining time.
|
||||
m_oRemainingTime = null;
|
||||
break;
|
||||
|
||||
case InUseStateEnum.Reserved:
|
||||
// Todo: Handle p_oFrom == null here.
|
||||
// Todo: Handle p_oDuration == null here.
|
||||
m_oStateInfo = new StateInfo(
|
||||
m_oDateTimeNowProvider,
|
||||
p_oFrom.Value,
|
||||
p_strMailAddress,
|
||||
p_strCode);
|
||||
|
||||
// Set value to null. Otherwise potentially obsolete value will be taken remaining time.
|
||||
m_oRemainingTime = null;
|
||||
break;
|
||||
|
||||
case InUseStateEnum.Booked:
|
||||
// Todo: Handle p_oFrom == null here.
|
||||
// Todo: Clearify question: What to do if code changes form one value to another? This should never happen.
|
||||
// Todo: Clearify question: What to do if from time changes form one value to another? This should never happen.
|
||||
m_oStateInfo = new StateInfo(
|
||||
p_oFrom.Value,
|
||||
p_strMailAddress,
|
||||
p_strCode);
|
||||
|
||||
// Set value to null. Otherwise potentially obsolete value will be taken remaining time.
|
||||
m_oRemainingTime = null;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Todo: New state Busy has to be defined.
|
||||
break;
|
||||
}
|
||||
|
||||
if (l_oLastState != m_oStateInfo.Value
|
||||
&& notifyLevel == Bikes.Bike.BC.NotifyPropertyChangedLevel.All)
|
||||
{
|
||||
// State has changed, notify clients.
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(State)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If bike is reserved time raimaining while bike stays reserved, null otherwise.
|
||||
/// </summary>
|
||||
public TimeSpan? RemainingTime
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (m_oStateInfo.Value)
|
||||
{
|
||||
// State is either available or occupied.
|
||||
case InUseStateEnum.Disposable:
|
||||
case InUseStateEnum.Booked:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (m_oRemainingTime.HasValue == false)
|
||||
{
|
||||
// Value was not yet querried.
|
||||
// Do querry before returning object.
|
||||
m_oStateInfo.GetIsStillReserved(out m_oRemainingTime);
|
||||
}
|
||||
|
||||
return m_oRemainingTime;
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime? From
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_oStateInfo.From;
|
||||
}
|
||||
}
|
||||
|
||||
public string MailAddress
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_oStateInfo.MailAddress;
|
||||
}
|
||||
}
|
||||
|
||||
public string Code
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_oStateInfo.Code;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
80
TINKLib/Model/State/StateOccupiedInfo.cs
Normal file
80
TINKLib/Model/State/StateOccupiedInfo.cs
Normal file
|
@ -0,0 +1,80 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace TINK.Model.State
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages state booked.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public sealed class StateOccupiedInfo : BaseState, IBaseState, INotAvailableState
|
||||
{
|
||||
/// <summary>
|
||||
/// Prevents an invalid instance to be created.
|
||||
/// </summary>
|
||||
private StateOccupiedInfo() : base(InUseStateEnum.Booked)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an object holding booked state info.
|
||||
/// </summary>
|
||||
/// <param name="p_oFrom">Date time when bike was booked</param>
|
||||
/// <param name="p_strMailAddress"></param>
|
||||
/// <param name="p_strCode"></param>
|
||||
public StateOccupiedInfo(
|
||||
DateTime p_oFrom,
|
||||
string p_strMailAddress,
|
||||
string p_strCode) : base(InUseStateEnum.Booked)
|
||||
{
|
||||
From = p_oFrom;
|
||||
MailAddress = p_strMailAddress;
|
||||
Code = p_strCode;
|
||||
}
|
||||
|
||||
/// <summary> Constructor for Json serialization. </summary>
|
||||
/// <param name="Value">Unused value.</param>
|
||||
/// <param name="From">Date time when bike was booked</param>
|
||||
/// <param name="MailAddress"></param>
|
||||
/// <param name="Code"></param>
|
||||
[JsonConstructor]
|
||||
private StateOccupiedInfo(
|
||||
InUseStateEnum Value,
|
||||
DateTime From,
|
||||
string MailAddress,
|
||||
string Code) : this(From, MailAddress, Code)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the info that state is reserved.
|
||||
/// Setter exists only for serialization purposes.
|
||||
/// </summary>
|
||||
public override InUseStateEnum Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return InUseStateEnum.Booked;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevents an invalid instance to be created.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public DateTime From { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Mail address of user who bookec the bike.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string MailAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Booking code.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string Code { get; }
|
||||
}
|
||||
}
|
114
TINKLib/Model/State/StateRequestedInfo.cs
Normal file
114
TINKLib/Model/State/StateRequestedInfo.cs
Normal file
|
@ -0,0 +1,114 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace TINK.Model.State
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages state reserved.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public sealed class StateRequestedInfo : BaseState, IBaseState, INotAvailableState
|
||||
{
|
||||
// Maximum time while reserving request is kept.
|
||||
public static readonly TimeSpan MaximumReserveTime = new TimeSpan(0, 15, 0); // 15 mins
|
||||
|
||||
// Reference to date time provider.
|
||||
private Func<DateTime> m_oDateTimeNowProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents an invalid instance to be created.
|
||||
/// Used by serializer only.
|
||||
/// </summary>
|
||||
private StateRequestedInfo() : base(InUseStateEnum.Reserved)
|
||||
{
|
||||
// Is called in context of JSON deserialization.
|
||||
m_oDateTimeNowProvider = () => DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reservation performed with other device/ before start of app.
|
||||
/// Date time info when bike was reserved has been received from webserver.
|
||||
/// </summary>
|
||||
/// <param name="p_oRemainingTime">Time span which holds duration how long bike still will be reserved.</param>
|
||||
[JsonConstructor]
|
||||
private StateRequestedInfo(
|
||||
InUseStateEnum Value,
|
||||
DateTime From,
|
||||
string MailAddress,
|
||||
string Code) : this(() => DateTime.Now, From, MailAddress, Code)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reservation performed with other device/ before start of app.
|
||||
/// Date time info when bike was reserved has been received from webserver.
|
||||
/// </summary>
|
||||
/// <param name="p_oDateTimeNowProvider">
|
||||
/// Used to to provide current date time information for potential calls of <seealso cref="GetIsStillReserved"/>.
|
||||
/// Not used to calculate remaining time because this duration whould always be shorter as the one received from webserver.
|
||||
/// </param>
|
||||
/// <param name="p_oRemainingTime">Time span which holds duration how long bike still will be reserved.</param>
|
||||
public StateRequestedInfo(
|
||||
Func<DateTime> p_oDateTimeNowProvider,
|
||||
DateTime p_oFrom,
|
||||
string p_strMailAddress,
|
||||
string p_strCode) : base(InUseStateEnum.Reserved)
|
||||
{
|
||||
m_oDateTimeNowProvider = p_oDateTimeNowProvider ?? (() => DateTime.Now);
|
||||
From = p_oFrom;
|
||||
MailAddress = p_strMailAddress;
|
||||
Code = p_strCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries update
|
||||
/// </summary>
|
||||
/// <returns>True if reservation span has not exeeded and state remains reserved, false otherwise.</returns>
|
||||
/// <todo>Implement logging of time stamps.</todo>
|
||||
public bool GetIsStillReserved(out TimeSpan? p_oRemainingTime)
|
||||
{
|
||||
var l_oTimeReserved = m_oDateTimeNowProvider().Subtract(From);
|
||||
if (l_oTimeReserved > MaximumReserveTime)
|
||||
|
||||
{
|
||||
// Reservation has elapsed. To not update remaining time.
|
||||
p_oRemainingTime = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
p_oRemainingTime = MaximumReserveTime - l_oTimeReserved;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State reserved.
|
||||
/// Setter exists only for serialization purposes.
|
||||
/// </summary>
|
||||
public override InUseStateEnum Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return InUseStateEnum.Reserved;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Date time when bike was reserved.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public DateTime From { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Mail address of user who reserved the bike.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string MailAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Booking code.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string Code { get; }
|
||||
}
|
||||
}
|
19
TINKLib/Model/Station/IStation.cs
Normal file
19
TINKLib/Model/Station/IStation.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace TINK.Model.Station
|
||||
{
|
||||
public interface IStation
|
||||
{
|
||||
/// <summary> Holds the unique id of the station.c</summary>
|
||||
int Id { get; }
|
||||
|
||||
/// <summary> Holds the group to which the station belongs.</summary>
|
||||
IEnumerable<string> Group { get; }
|
||||
|
||||
/// <summary> Gets the name of the station.</summary>
|
||||
string StationName { get; }
|
||||
|
||||
/// <summary> Holds the gps- position of the station.</summary>
|
||||
Position Position { get; }
|
||||
}
|
||||
}
|
20
TINKLib/Model/Station/NullStation.cs
Normal file
20
TINKLib/Model/Station/NullStation.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace TINK.Model.Station
|
||||
{
|
||||
/// <summary> Holds object representing null station.</summary>
|
||||
public class NullStation : IStation
|
||||
{
|
||||
/// <summary> Holds the unique id of the station.c</summary>
|
||||
public int Id => -1;
|
||||
|
||||
/// <summary> Holds the group to which the station belongs.</summary>
|
||||
public IEnumerable<string> Group => new List<string>();
|
||||
|
||||
/// <summary> Gets the name of the station.</summary>
|
||||
public string StationName => string.Empty;
|
||||
|
||||
/// <summary> Holds the gps- position of the station.</summary>
|
||||
public Position Position => new Position(double.NaN, double.NaN);
|
||||
}
|
||||
}
|
49
TINKLib/Model/Station/Position.cs
Normal file
49
TINKLib/Model/Station/Position.cs
Normal file
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
|
||||
namespace TINK.Model.Station
|
||||
{
|
||||
public class Position
|
||||
{
|
||||
private const double PRECISSION_LATITUDE_LONGITUDE = 0.000000000000001;
|
||||
|
||||
public Position()
|
||||
{
|
||||
}
|
||||
|
||||
public Position(double p_dLatitude, double p_dLongitude)
|
||||
{
|
||||
Latitude = p_dLatitude;
|
||||
Longitude = p_dLongitude;
|
||||
}
|
||||
|
||||
public double Latitude { get; private set; }
|
||||
|
||||
public double Longitude { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Compares position with a target position.
|
||||
/// </summary>
|
||||
/// <param name="p_oTarget">Target position to compare with.</param>
|
||||
/// <returns>True if positions are equal.</returns>
|
||||
public override bool Equals(object p_oTarget)
|
||||
{
|
||||
var l_oTarget = p_oTarget as Position;
|
||||
if (l_oTarget is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Math.Abs(Latitude - l_oTarget.Latitude) < PRECISSION_LATITUDE_LONGITUDE
|
||||
&& Math.Abs(Longitude - l_oTarget.Longitude) < PRECISSION_LATITUDE_LONGITUDE;
|
||||
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCode = -1416534245;
|
||||
hashCode = hashCode * -1521134295 + Latitude.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + Longitude.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
}
|
38
TINKLib/Model/Station/Station.cs
Normal file
38
TINKLib/Model/Station/Station.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TINK.Model.Station
|
||||
{
|
||||
/// <summary> Holds station info. </summary>
|
||||
public class Station : IStation
|
||||
{
|
||||
/// <summary> Constructs a station object.</summary>
|
||||
/// <param name="p_iId">Id of the station.</param>
|
||||
/// <param name="p_oGroup">Group (TINK, Konrad) to which station is related.</param>
|
||||
/// <param name="p_oPosition">GPS- position of the station.</param>
|
||||
/// <param name="p_strStationName">Name of the station.</param>
|
||||
public Station(
|
||||
int p_iId,
|
||||
IEnumerable<string> p_oGroup,
|
||||
Position p_oPosition,
|
||||
string p_strStationName = "")
|
||||
{
|
||||
Id = p_iId;
|
||||
Group = p_oGroup ?? throw new ArgumentException("Can not construct station object. Group of stations must not be null.");
|
||||
Position = p_oPosition;
|
||||
StationName = p_strStationName ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary> Holds the unique id of the station.c</summary>
|
||||
public int Id { get; }
|
||||
|
||||
/// <summary> Holds the group to which the station belongs.</summary>
|
||||
public IEnumerable<string> Group { get; }
|
||||
|
||||
/// <summary> Gets the name of the station.</summary>
|
||||
public string StationName { get; }
|
||||
|
||||
/// <summary> Holds the gps- position of the station.</summary>
|
||||
public Position Position { get; }
|
||||
}
|
||||
}
|
97
TINKLib/Model/Station/StationCollection.cs
Normal file
97
TINKLib/Model/Station/StationCollection.cs
Normal file
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TINK.Model.Station
|
||||
{
|
||||
public class StationDictionary : IEnumerable<Station>
|
||||
{
|
||||
/// <summary> Holds the list of stations. </summary>
|
||||
private readonly IDictionary<int, Station> m_oStationDictionary;
|
||||
|
||||
/// <summary> Count of stations. </summary>
|
||||
public int Count { get { return m_oStationDictionary.Count; } }
|
||||
|
||||
public Version CopriVersion { get; }
|
||||
|
||||
/// <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)
|
||||
{
|
||||
m_oStationDictionary = p_oStations ?? new Dictionary<int, Station>();
|
||||
|
||||
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()
|
||||
{
|
||||
return m_oStationDictionary.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deteermines whether a station by given key exists.
|
||||
/// </summary>
|
||||
/// <param name="p_strKey">Key to check.</param>
|
||||
/// <returns>True if station exists.</returns>
|
||||
public bool ContainsKey(int p_strKey)
|
||||
{
|
||||
return m_oStationDictionary.ContainsKey(p_strKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a station by station id.
|
||||
/// </summary>
|
||||
/// <param name="p_iId"></param>
|
||||
public void RemoveById(int p_iId)
|
||||
{
|
||||
if (!m_oStationDictionary.ContainsKey(p_iId))
|
||||
{
|
||||
// Nothing to do if there is no station with given name.
|
||||
return;
|
||||
}
|
||||
|
||||
m_oStationDictionary.Remove(p_iId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a station by station name.
|
||||
/// </summary>
|
||||
/// <param name="p_iId"></param>
|
||||
public Station GetById(int p_iId)
|
||||
{
|
||||
if (!m_oStationDictionary.ContainsKey(p_iId))
|
||||
{
|
||||
// Nothing to do if there is no station with given name.
|
||||
return null;
|
||||
}
|
||||
|
||||
return m_oStationDictionary[p_iId];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a station to dictionary of stations.
|
||||
/// </summary>
|
||||
/// <param name="p_oStation"></param>
|
||||
public void Add(Station p_oStation)
|
||||
{
|
||||
if (p_oStation == null)
|
||||
{
|
||||
throw new ArgumentException("Can not add empty station to collection of stations.");
|
||||
}
|
||||
|
||||
if (m_oStationDictionary.ContainsKey(p_oStation.Id))
|
||||
{
|
||||
throw new ArgumentException(string.Format("Can not add station {0} to collection of stations. A station with given name already exists.", p_oStation.Id));
|
||||
}
|
||||
|
||||
m_oStationDictionary.Add(p_oStation.Id, p_oStation);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return m_oStationDictionary.Values.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
423
TINKLib/Model/TinkApp.cs
Normal file
423
TINKLib/Model/TinkApp.cs
Normal file
|
@ -0,0 +1,423 @@
|
|||
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 Plugin.Permissions.Abstractions;
|
||||
using TINK.Services.BluetoothLock.Crypto;
|
||||
using TINK.ViewModel.Map;
|
||||
using TINK.ViewModel.Settings;
|
||||
using TINK.Services;
|
||||
using TINK.Services.BluetoothLock.BLE;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace TINK.Model
|
||||
{
|
||||
[DataContract]
|
||||
public class TinkApp : ITinkApp
|
||||
{
|
||||
/// <summary> Delegate used by login view to commit user name and password. </summary>
|
||||
/// <param name="p_strMailAddress">Mail address used as id login.</param>
|
||||
/// <param name="p_strPassword">Password for login.</param>
|
||||
/// <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>
|
||||
public static string MerchantId => "oiF2kahH";
|
||||
|
||||
/// <summary>
|
||||
/// Holds status about whants new page.
|
||||
/// </summary>
|
||||
public WhatsNew WhatsNew { get; private set; }
|
||||
|
||||
/// <summary>Sets flag whats new page was already shown to true. </summary>
|
||||
public void SetWhatsNewWasShown() => WhatsNew = WhatsNew.SetWasShown();
|
||||
|
||||
/// <summary>Holds uris of copri servers. </summary>
|
||||
public CopriServerUriList Uris { get; }
|
||||
|
||||
/// <summary> Holds the filters loaded from settings. </summary>
|
||||
public IGroupFilterSettings FilterGroupSetting { get; set; }
|
||||
|
||||
/// <summary> Holds the filter which is applied on the map view. Either TINK or Konrad stations are displayed. </summary>
|
||||
private IGroupFilterMapPage m_oFilterDictionaryMapPage;
|
||||
|
||||
/// <summary> Holds the filter which is applied on the map view. Either TINK or Konrad stations are displayed. </summary>
|
||||
public IGroupFilterMapPage GroupFilterMapPage
|
||||
{
|
||||
get => m_oFilterDictionaryMapPage;
|
||||
set => m_oFilterDictionaryMapPage = value ?? new GroupFilterMapPage();
|
||||
}
|
||||
|
||||
/// <summary> Value indicating whether map is centerted to current position or not. </summary>
|
||||
public bool CenterMapToCurrentLocation { get; set; }
|
||||
|
||||
/// <summary> Gets the minimum logging level. </summary>
|
||||
public LogEventLevel MinimumLogEventLevel { get; set; }
|
||||
|
||||
/// <summary> Holds the uri which is applied after restart. </summary>
|
||||
public Uri NextActiveUri { get; set; }
|
||||
|
||||
/// <summary> Saves object to file. </summary>
|
||||
public void Save()
|
||||
=> JsonSettingsDictionary.Serialize(
|
||||
SettingsFileFolder,
|
||||
new Dictionary<string, string>()
|
||||
.SetGroupFilterMapPage(GroupFilterMapPage)
|
||||
.SetCopriHostUri(NextActiveUri.AbsoluteUri)
|
||||
.SetPollingParameters(Polling)
|
||||
.SetGroupFilterSettings(FilterGroupSetting)
|
||||
.SetAppVersion(AppVersion)
|
||||
.SetMinimumLoggingLevel(MinimumLogEventLevel)
|
||||
.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));
|
||||
|
||||
/// <summary>
|
||||
/// Update connector from filters when
|
||||
/// - login state changes
|
||||
/// - view is toggled (TINK to Kornrad and vice versa)
|
||||
/// </summary>
|
||||
public void UpdateConnector()
|
||||
{
|
||||
// Create filtered connector.
|
||||
m_oConnector = FilteredConnectorFactory.Create(
|
||||
FilterGroupSetting.DoFilter(ActiveUser.DoFilter(GroupFilterMapPage.DoFilter())),
|
||||
m_oConnector.Connector);
|
||||
}
|
||||
|
||||
/// <summary>Polling periode.</summary>
|
||||
public PollingParameters Polling { get; set; }
|
||||
|
||||
public TimeSpan ExpiresAfter { get; set; }
|
||||
|
||||
/// <summary> Holds the version of the app.</summary>
|
||||
public Version AppVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the default polling value.
|
||||
/// </summary>
|
||||
public TimeSpan DefaultPolling => new TimeSpan(0, 0, 10);
|
||||
|
||||
/// <summary> Constructs TinkApp object. </summary>
|
||||
/// <param name="settings"></param>
|
||||
/// <param name="accountStore"></param>
|
||||
/// <param name="passwordValidator"></param>
|
||||
/// <param name="p_oConnectorFactory"></param>
|
||||
/// <param name="geolocationService">Null in productive context. Service to querry geoloation for testing purposes. Parameter can be made optional.</param>
|
||||
/// <param name="locksService">Null in productive context. Service to control locks/ get locks information for testing proposes. Parameter can be made optional.</param>
|
||||
/// <param name="device">Object allowing platform specific operations.</param>
|
||||
/// <param name="specialFolder"></param>
|
||||
/// <param name="p_oDateTimeProvider"></param>
|
||||
/// <param name="isConnectedFunc">True if connector has access to copri server, false if cached values are used.</param>
|
||||
/// <param name="currentVersion">Version of the app. If null version is set to a fixed dummy value (3.0.122) for testing purposes.</param>
|
||||
/// <param name="lastVersion">Version of app which was used before this session.</param>
|
||||
/// <param name="whatsNewShownInVersion"> 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.
|
||||
/// /// </param>
|
||||
public TinkApp(
|
||||
Settings.Settings settings,
|
||||
IStore accountStore,
|
||||
Func<bool, Uri, string, string, TimeSpan, IConnector> connectorFactory,
|
||||
IGeolocation geolocationService,
|
||||
IGeolodationDependent geolodationServiceDependent,
|
||||
ILocksService locksService,
|
||||
IDevice device,
|
||||
ISpecialFolder specialFolder,
|
||||
ICipher cipher,
|
||||
IPermissions permissions = null,
|
||||
object arendiCentral = null,
|
||||
Func<bool> isConnectedFunc = null,
|
||||
Action<SendOrPostCallback, object> 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<ILocksService> { locksService }
|
||||
: new HashSet<ILocksService> {
|
||||
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<object>(
|
||||
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();
|
||||
|
||||
if (settings.ActiveUri == new Uri(CopriServerUriList.TINK_LIVE) ||
|
||||
settings.ActiveUri == new Uri(CopriServerUriList.TINK_DEVEL))
|
||||
{
|
||||
FilterGroupSetting = settings.GroupFilterSettings;
|
||||
GroupFilterMapPage = settings.GroupFilterMapPage;
|
||||
//} else if (settings.ActiveUri == new Uri(CopriServerUriList.SHAREE_LIVE) ||
|
||||
// settings.ActiveUri == new Uri(CopriServerUriList.SHAREE_DEVEL))
|
||||
//{
|
||||
// FilterGroupSetting = new GroupFilterSettings(new Dictionary<string, FilterState> { { "300001", FilterState.On }, { "300029", FilterState.On } });
|
||||
// FilterGroupMapPage = new GroupFilterMapPage();
|
||||
} else
|
||||
{
|
||||
FilterGroupSetting = new GroupFilterSettings();
|
||||
GroupFilterMapPage = new GroupFilterMapPage();
|
||||
}
|
||||
|
||||
CenterMapToCurrentLocation = settings.CenterMapToCurrentLocation;
|
||||
|
||||
Device = 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();
|
||||
|
||||
SelectedStation = null;
|
||||
|
||||
ActiveUser = new User.User(
|
||||
accountStore,
|
||||
l_oAccount,
|
||||
device.GetIdentifier());
|
||||
|
||||
this.isConnectedFunc = isConnectedFunc ?? (() => CrossConnectivity.Current.IsConnected);
|
||||
|
||||
ExpiresAfter = settings.ExpiresAfter;
|
||||
|
||||
// Create filtered connector for offline mode.
|
||||
m_oConnector = FilteredConnectorFactory.Create(
|
||||
FilterGroupSetting.DoFilter(l_oAccount.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;
|
||||
|
||||
Permissions = permissions ??
|
||||
throw new ArgumentException("Can not instantiate TinkApp- object. Permissions object must never be null.");
|
||||
|
||||
WhatsNew = new WhatsNew(AppVersion, lastVersion, whatsNewShownInVersion);
|
||||
|
||||
if (Themes.Active.GetType().FullName == typeof(Themes.ShareeBike).FullName)
|
||||
return;
|
||||
|
||||
// Set active app theme
|
||||
ICollection<ResourceDictionary> mergedDictionaries = Application.Current.Resources.MergedDictionaries;
|
||||
if (mergedDictionaries == null)
|
||||
{
|
||||
Log.ForContext<TinkApp>().Error("No merged dictionary available.");
|
||||
return;
|
||||
}
|
||||
|
||||
mergedDictionaries.Clear();
|
||||
|
||||
if (Themes.Active.GetType().FullName == typeof(Themes.Konrad).FullName)
|
||||
{
|
||||
mergedDictionaries.Add(new Themes.Konrad());
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<TinkApp>().Debug($"No theme {Themes.Active} found.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Holds the user of the app. </summary>
|
||||
[DataMember]
|
||||
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; }
|
||||
|
||||
/// <summary> Holds delegate to determine whether device is connected or not.</summary>
|
||||
private Func<bool> isConnectedFunc;
|
||||
|
||||
/// <summary> Gets whether device is connected to internet or not. </summary>
|
||||
public bool GetIsConnected() => isConnectedFunc();
|
||||
|
||||
/// <summary> Holds the folder where settings files are stored. </summary>
|
||||
public string SettingsFileFolder { get; }
|
||||
|
||||
/// <summary> Holds folder parent of the folder where log files are stored. </summary>
|
||||
public string LogFileParentFolder => LogToExternalFolder && !string.IsNullOrEmpty(ExternalFolder) ? ExternalFolder : SettingsFileFolder;
|
||||
|
||||
/// <summary> Holds a value indicating whether to log to external or internal folder. </summary>
|
||||
public bool LogToExternalFolder { get; set; }
|
||||
|
||||
/// <summary> Holds a value indicating whether Site caching is on or off. </summary>
|
||||
public bool IsSiteCachingOn { get; set; }
|
||||
|
||||
/// <summary> External folder. </summary>
|
||||
public string ExternalFolder { get; }
|
||||
|
||||
public ICipher Cipher { get; }
|
||||
|
||||
/// <summary> Name of the station which is selected. </summary>
|
||||
public int? SelectedStation { get; set; }
|
||||
|
||||
/// <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; }
|
||||
|
||||
/// <summary> Holds the object which provides offline data.</summary>
|
||||
private IFilteredConnector m_oConnector;
|
||||
|
||||
/// <summary> Holds the system to copri.</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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; }
|
||||
|
||||
/// <summary> Manages the different types of LocksService objects.</summary>
|
||||
public ServicesContainerMutable<object> Themes { get; }
|
||||
|
||||
/// <summary> Object to switch logging level. </summary>
|
||||
private LoggingLevelSwitch m_oLoggingLevelSwitch;
|
||||
|
||||
/// <summary>
|
||||
/// Object to allow swithing logging level
|
||||
/// </summary>
|
||||
public LoggingLevelSwitch Level
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_oLoggingLevelSwitch == null)
|
||||
{
|
||||
m_oLoggingLevelSwitch = new LoggingLevelSwitch
|
||||
{
|
||||
|
||||
// Set warning level to error.
|
||||
MinimumLevel = Settings.Settings.DEFAULTLOGGINLEVEL
|
||||
};
|
||||
}
|
||||
|
||||
return m_oLoggingLevelSwitch;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Updates logging level. </summary>
|
||||
/// <param name="p_oNewLevel">New level to set.</param>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
91
TINKLib/Model/User/Account/Account.cs
Normal file
91
TINKLib/Model/User/Account/Account.cs
Normal file
|
@ -0,0 +1,91 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TINK.Model.User.Account
|
||||
{
|
||||
/// <summary> Specifies extra user permissions. </summary>
|
||||
[Flags]
|
||||
public enum Permissions
|
||||
{
|
||||
None = 0, // No extra permissions.
|
||||
PickCopriServer = 2, // Allows user to switch COPRI server.
|
||||
ManageCopriCacheExpiration = 4, // Allows to manage the livetime of COPRI cache entries.
|
||||
ManagePolling = 8, // Turn polling off or on and set pollig frequency.
|
||||
PickLockServiceImplementation = 16, // Allows to pick the implementation which controls bluetooth lock mangement.
|
||||
PickLocationServiceImplementation = 32, // Allows to pick the implementation which gets location information.
|
||||
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
|
||||
All = PickCopriServer +
|
||||
ManageCopriCacheExpiration +
|
||||
ManagePolling +
|
||||
PickLockServiceImplementation +
|
||||
PickLocationServiceImplementation +
|
||||
PickLoggingLevel +
|
||||
ShowDiagnostics +
|
||||
SwitchNoSiteCaching,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies parts of account data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Usage: Account can be valid (user and password set) partly valid or completely invalid.
|
||||
/// </remarks>
|
||||
[Flags]
|
||||
public enum Elements
|
||||
{
|
||||
None = 0,
|
||||
Mail = 1,
|
||||
Password = 2,
|
||||
Account = Mail + Password
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds account data.
|
||||
/// </summary>
|
||||
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="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,
|
||||
Permissions debugLevel = Permissions.None)
|
||||
{
|
||||
Mail = p_oMail;
|
||||
Pwd = p_Pwd;
|
||||
SessionCookie = p_oSessionCookie;
|
||||
DebugLevel = debugLevel;
|
||||
Group = p_strGroup != null
|
||||
? new HashSet<string>(p_strGroup).ToList()
|
||||
: throw new ArgumentException("Can not instantiate account object. Reference to group list must not be empty.");
|
||||
}
|
||||
|
||||
public Account(IAccount p_oSource) : this(p_oSource?.Mail, p_oSource?.Pwd, p_oSource?.SessionCookie, p_oSource?.Group, p_oSource?.DebugLevel ?? Permissions.None)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Mail address.</summary>
|
||||
public string Mail { get; }
|
||||
|
||||
/// <summary>Password of the account.</summary>
|
||||
public string Pwd { get; }
|
||||
|
||||
/// <summary>Session cookie used to sign in to copri.</summary>
|
||||
public string SessionCookie { get; }
|
||||
|
||||
/// <summary>Debug level used to determine which features are available.</summary>
|
||||
public Permissions DebugLevel { get; }
|
||||
|
||||
/// <summary> Holds the group of the bike (TINK, Konrad, ...).</summary>
|
||||
public IEnumerable<string> Group { get; }
|
||||
}
|
||||
}
|
34
TINKLib/Model/User/Account/AccountExtensions.cs
Normal file
34
TINKLib/Model/User/Account/AccountExtensions.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System.Collections.Generic;
|
||||
using TINK.Model.Connector.Filter;
|
||||
|
||||
namespace TINK.Model.User.Account
|
||||
{
|
||||
public static class AccountExtensions
|
||||
{
|
||||
/// <summary> Gets information whether user is logged in or not from account object. </summary>
|
||||
/// <param name="p_oAccount">Object to get information from.</param>
|
||||
/// <returns>True if user is logged in, false if not.</returns>
|
||||
public static bool GetIsLoggedIn(this IAccount p_oAccount)
|
||||
{
|
||||
return !string.IsNullOrEmpty(p_oAccount.Mail)
|
||||
&& !string.IsNullOrEmpty(p_oAccount.SessionCookie);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters bike groups depending on whether user has access to all groups of bikes.
|
||||
/// Some user may be "TINK"- user only, some "Konrad" and some may be "TINK" and "Konrad" users.
|
||||
/// </summary>
|
||||
/// <param name="account">Account to filter with.</param>
|
||||
/// <param name="filter">Groups to filter.</param>
|
||||
/// <returns>Filtered bike groups.</returns>
|
||||
public static IEnumerable<string> DoFilter(
|
||||
this IAccount account,
|
||||
IEnumerable<string> filter)
|
||||
{
|
||||
|
||||
return GetIsLoggedIn(account)
|
||||
? GroupFilterFactory.Create(account.Group).DoFilter(filter) // Filter if user is logged in.
|
||||
: new NullGroupFilter().DoFilter(filter); // Do not filter if no user is logged in.
|
||||
}
|
||||
}
|
||||
}
|
74
TINKLib/Model/User/Account/AccountMutable.cs
Normal file
74
TINKLib/Model/User/Account/AccountMutable.cs
Normal file
|
@ -0,0 +1,74 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace TINK.Model.User.Account
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds email address and password.
|
||||
/// </summary>
|
||||
public class AccountMutable : IAccount
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the account data.
|
||||
/// </summary>
|
||||
private Account m_oAccount;
|
||||
|
||||
/// <summary> Prevents an invalid instance to be created. </summary>
|
||||
private AccountMutable()
|
||||
{
|
||||
}
|
||||
|
||||
public AccountMutable(IAccount p_oSource)
|
||||
{
|
||||
m_oAccount = new Account(p_oSource);
|
||||
}
|
||||
|
||||
public void Copy(IAccount p_oSource)
|
||||
{
|
||||
m_oAccount = new Account(p_oSource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mail address.
|
||||
/// </summary>
|
||||
public string Mail
|
||||
{
|
||||
get { return m_oAccount.Mail; }
|
||||
set { m_oAccount = new Account(value, m_oAccount.Pwd, m_oAccount.SessionCookie, m_oAccount.Group, m_oAccount.DebugLevel); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Password of the account.
|
||||
/// </summary>
|
||||
public string Pwd
|
||||
{
|
||||
get { return m_oAccount.Pwd; }
|
||||
set { m_oAccount = new Account(m_oAccount.Mail, value, m_oAccount.SessionCookie, m_oAccount.Group, m_oAccount.DebugLevel); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Session cookie used to sign in to copri.
|
||||
/// </summary>
|
||||
public string SessionCookie
|
||||
{
|
||||
get { return m_oAccount.SessionCookie; }
|
||||
set { m_oAccount = new Account(m_oAccount.Mail, m_oAccount.Pwd, value, m_oAccount.Group, m_oAccount.DebugLevel); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the group of the bike (TINK, Konrad, ...).
|
||||
/// </summary>
|
||||
public IEnumerable<string> Group
|
||||
{
|
||||
get { return m_oAccount.Group; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Debug level used to determine which features are available.
|
||||
/// </summary>
|
||||
public Permissions DebugLevel
|
||||
{
|
||||
get { return m_oAccount.DebugLevel; }
|
||||
set { m_oAccount = new Account(m_oAccount.Mail, m_oAccount.Pwd, m_oAccount.SessionCookie, m_oAccount.Group, value); }
|
||||
}
|
||||
}
|
||||
}
|
18
TINKLib/Model/User/Account/EmptyAccount.cs
Normal file
18
TINKLib/Model/User/Account/EmptyAccount.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace TINK.Model.User.Account
|
||||
{
|
||||
/// <summary> Represents an empty account.</summary>
|
||||
public class EmptyAccount : IAccount
|
||||
{
|
||||
public string Mail => null;
|
||||
|
||||
public string Pwd => null;
|
||||
|
||||
public string SessionCookie => null;
|
||||
|
||||
public Permissions DebugLevel => Permissions.None;
|
||||
|
||||
public IEnumerable<string> Group => new List<string>();
|
||||
}
|
||||
}
|
25
TINKLib/Model/User/Account/IAccount.cs
Normal file
25
TINKLib/Model/User/Account/IAccount.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace TINK.Model.User.Account
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds account data.
|
||||
/// </summary>
|
||||
public interface IAccount
|
||||
{
|
||||
/// <summary>Mail address.</summary>
|
||||
string Mail { get; }
|
||||
|
||||
/// <summary>Password of the account.</summary>
|
||||
string Pwd { get; }
|
||||
|
||||
/// <summary>Session cookie used to sign in to copri.</summary>
|
||||
string SessionCookie { get; }
|
||||
|
||||
/// <summary>Debug level used to determine which features are available.</summary>
|
||||
Permissions DebugLevel { get; }
|
||||
|
||||
/// <summary> Holds the group of the bike (TINK, Konrad, ...).</summary>
|
||||
IEnumerable<string> Group { get; }
|
||||
}
|
||||
}
|
26
TINKLib/Model/User/Account/IStore.cs
Normal file
26
TINKLib/Model/User/Account/IStore.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
namespace TINK.Model.User.Account
|
||||
{
|
||||
/// <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();
|
||||
|
||||
/// <summary>
|
||||
/// Writes mail address and password to account store.
|
||||
/// </summary>
|
||||
/// <param name="p_oMailAndPwd"></param>
|
||||
void Save(IAccount p_oMailAndPwd);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes mail address and password from account store.
|
||||
/// </summary>
|
||||
/// <returns> Empty account instance if deleting succeeded.</returns>
|
||||
IAccount Delete(IAccount p_oMailAndPwd);
|
||||
}
|
||||
}
|
167
TINKLib/Model/User/Account/Validator.cs
Normal file
167
TINKLib/Model/User/Account/Validator.cs
Normal file
|
@ -0,0 +1,167 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TINK.Model.User.Account
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the state of mail and password and information about some invalid parts if there are.
|
||||
/// </summary>
|
||||
public class State
|
||||
{
|
||||
/// <summary>
|
||||
/// Consider state to be invalid after construction.
|
||||
/// </summary>
|
||||
private Elements m_eElements = Elements.None;
|
||||
|
||||
private Dictionary<Elements, string> m_oDescription = new Dictionary<Elements, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Constructs object to state all entries are valid.
|
||||
/// </summary>
|
||||
public State()
|
||||
{
|
||||
m_eElements = Elements.Account;
|
||||
|
||||
m_oDescription = new Dictionary<Elements, string>
|
||||
{
|
||||
{ Elements.None, string.Empty },
|
||||
{ Elements.Account, string.Empty }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs object to state some/ all elements are invalid.
|
||||
/// </summary>
|
||||
/// <param name="p_oValidParts">Specifies the parts which are invalid.</param>
|
||||
/// <param name="p_oDescription">Description of invalid parts.</param>
|
||||
public State(Elements p_oValidParts, Dictionary<Elements, string> p_oDescription)
|
||||
{
|
||||
m_eElements = p_oValidParts;
|
||||
|
||||
m_oDescription = p_oDescription ?? new Dictionary<Elements, string>();
|
||||
|
||||
// Ensure consistency
|
||||
foreach (Elements l_oElement in Enum.GetValues(typeof(Elements)))
|
||||
{
|
||||
if (!m_oDescription.ContainsKey(l_oElement))
|
||||
{
|
||||
switch (l_oElement)
|
||||
{
|
||||
case Elements.Account:
|
||||
case Elements.None:
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
m_oDescription.Add(l_oElement, string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if account is valid.
|
||||
/// </summary>
|
||||
public bool IsValid { get { return ValidElement == Elements.Account; } }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies if both mail and password are valid, one of them or none.
|
||||
/// </summary>
|
||||
public Elements ValidElement
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_eElements;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the message about invalid elements.
|
||||
/// </summary>
|
||||
public Dictionary<Elements, string> Description
|
||||
{
|
||||
get
|
||||
{
|
||||
var l_oUserFriendlyDescription = new Dictionary<Elements, string>();
|
||||
foreach (Elements l_oElement in Enum.GetValues(typeof(Elements)))
|
||||
{
|
||||
switch (l_oElement)
|
||||
{
|
||||
case Elements.Account:
|
||||
case Elements.None:
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
l_oUserFriendlyDescription.Add(
|
||||
l_oElement,
|
||||
m_oDescription.ContainsKey(l_oElement) ? m_oDescription[l_oElement] : string.Empty);
|
||||
}
|
||||
|
||||
l_oUserFriendlyDescription.Add(
|
||||
Elements.Account,
|
||||
string.Join(";", l_oUserFriendlyDescription.Where(x => x.Value.Length > 0).Select(x => x.Value).ToArray()));
|
||||
|
||||
return l_oUserFriendlyDescription ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies if a password is valid or not.
|
||||
/// </summary>
|
||||
/// <param name="p_strMail"></param>
|
||||
/// <param name="p_strPassword"></param>
|
||||
/// <returns></returns>
|
||||
public delegate State PasswordValidator(string p_strMail, string p_strPassword);
|
||||
|
||||
public static class Validator
|
||||
{
|
||||
public static State ValidateMailAndPasswordDelegate(string p_strMail, string p_strPassword)
|
||||
{
|
||||
var l_oElements = Elements.None;
|
||||
var l_oDescription = new Dictionary<Elements, string>();
|
||||
|
||||
// Validate mail address.
|
||||
if (string.IsNullOrEmpty(p_strMail))
|
||||
{
|
||||
l_oDescription.Add(Elements.Mail, "Email Addresse darf nicht leer sein.");
|
||||
}
|
||||
else if (p_strMail.ToString().Split('@').Length < 2)
|
||||
{
|
||||
l_oDescription.Add(Elements.Mail, "Email Adresse mit Zeichen \"@\" enthalten.");
|
||||
}
|
||||
else if (p_strMail.ToString().Split('@')[0].Length <= 0)
|
||||
{
|
||||
l_oDescription.Add(Elements.Mail, "Benutzername in Email Adresse darf nicht leer sein.");
|
||||
}
|
||||
else if (p_strMail.ToString().Split('@')[1].Length <= 0)
|
||||
{
|
||||
// Data has been entered
|
||||
l_oDescription.Add(Elements.Mail, "Domain- Name in Email Adresse darf nicht leer sein.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Input mail address is ok
|
||||
l_oElements = Elements.Mail;
|
||||
l_oDescription.Add(Elements.Mail, string.Empty);
|
||||
}
|
||||
|
||||
// Validate password.
|
||||
if (string.IsNullOrEmpty(p_strPassword) || p_strPassword.Length < 8)
|
||||
{
|
||||
// Data has been entered
|
||||
l_oDescription.Add(Elements.Password, "Passwort is zu kurz.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Password is ok
|
||||
l_oElements |= Elements.Password;
|
||||
l_oDescription.Add(Elements.Password, string.Empty);
|
||||
}
|
||||
|
||||
return new State(l_oElements, l_oDescription);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
16
TINKLib/Model/User/IUser.cs
Normal file
16
TINKLib/Model/User/IUser.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using TINK.Model.User.Account;
|
||||
|
||||
namespace TINK.Model.User
|
||||
{
|
||||
public interface IUser
|
||||
{
|
||||
/// <summary> Holds a value indicating whether user is logged in or not.</summary>
|
||||
bool IsLoggedIn { get; }
|
||||
|
||||
/// <summary> Holds the mail address. </summary>
|
||||
string Mail { get; }
|
||||
|
||||
/// <summary>Holds the debug level.</summary>
|
||||
Permissions DebugLevel { get; }
|
||||
}
|
||||
}
|
154
TINKLib/Model/User/User.cs
Normal file
154
TINKLib/Model/User/User.cs
Normal file
|
@ -0,0 +1,154 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TINK.Model.User.Account;
|
||||
|
||||
namespace TINK.Model.User
|
||||
{
|
||||
public delegate void LoginStateChangedDelegate(object p_oSender, EventArgs p_oEventArgs);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Manages user of the app.
|
||||
/// </summary>
|
||||
public class User : IUser
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds account data.
|
||||
/// </summary>
|
||||
private readonly AccountMutable m_oAccount;
|
||||
|
||||
/// <summary>
|
||||
/// Provides storing functionality.
|
||||
/// </summary>
|
||||
private IStore m_oStore;
|
||||
|
||||
/// <summary> Holds the id of the device. </summary>
|
||||
public string DeviceId { get; }
|
||||
|
||||
/// Loads user name and passwort from account store.
|
||||
/// </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)
|
||||
{
|
||||
m_oStore = p_oAccountStore
|
||||
?? throw new ArgumentException("Can not instantiate user- object. No store functionality available.");
|
||||
DeviceId = p_strDeviceId;
|
||||
m_oAccount = new AccountMutable(p_oAccount);
|
||||
}
|
||||
|
||||
/// <summary> Is fired wheneverlogin state changes. </summary>
|
||||
public event LoginStateChangedDelegate StateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Holds a value indicating whether user is logged in or not.
|
||||
/// </summary>
|
||||
public bool IsLoggedIn {
|
||||
get
|
||||
{
|
||||
return m_oAccount.GetIsLoggedIn();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the mail address.
|
||||
/// </summary>
|
||||
public string Mail
|
||||
{
|
||||
get { return m_oAccount.Mail; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sessiong cookie.
|
||||
/// </summary>
|
||||
public string SessionCookie
|
||||
{
|
||||
get { return m_oAccount.SessionCookie; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Holds the password.
|
||||
/// </summary>
|
||||
public string Password
|
||||
{
|
||||
get { return m_oAccount.Pwd; }
|
||||
}
|
||||
|
||||
/// <summary>Holds the debug level.</summary>
|
||||
public Permissions DebugLevel
|
||||
{
|
||||
get { return m_oAccount.DebugLevel; }
|
||||
}
|
||||
|
||||
/// <summary> Holds the group of the bike (TINK, Konrad, ...).</summary>
|
||||
public IEnumerable<string> Group { get { return m_oAccount.Group; } }
|
||||
|
||||
/// <summary> Logs in user. </summary>
|
||||
/// <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 CheckIsPasswordValid(string mail, string password)
|
||||
{
|
||||
if (IsLoggedIn)
|
||||
{
|
||||
throw new Exception($"Can not log in user {mail} because user {m_oAccount} is already logged in.");
|
||||
}
|
||||
|
||||
// Check if password might be valid before connecting to copri.
|
||||
var l_oResult = Validator.ValidateMailAndPasswordDelegate(mail, password);
|
||||
|
||||
if (!l_oResult.IsValid)
|
||||
{
|
||||
// Password is not valid.
|
||||
throw new ArgumentException(l_oResult.Description[Elements.Account]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Logs in user. </summary>
|
||||
/// <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)
|
||||
{
|
||||
// Update account instance from copri data.
|
||||
m_oAccount.Copy(account);
|
||||
|
||||
// Save data to store.
|
||||
m_oStore.Save(m_oAccount);
|
||||
|
||||
// Nothing to do because state did not change.
|
||||
StateChanged?.Invoke(this, new EventArgs());
|
||||
}
|
||||
|
||||
/// <summary> Logs in user</summary>
|
||||
/// <returns></returns>
|
||||
public void Logout()
|
||||
{
|
||||
var l_oPreviousState = IsLoggedIn;
|
||||
|
||||
m_oAccount.Copy(m_oStore.Delete(m_oAccount));
|
||||
|
||||
if (IsLoggedIn == l_oPreviousState)
|
||||
{
|
||||
// Nothing to do because state did not change.
|
||||
return;
|
||||
}
|
||||
|
||||
StateChanged?.Invoke(this, new EventArgs());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters bike groups depending on whether user has access to all groups of bikes.
|
||||
/// Some user may be "TINK"- user only, some "Konrad" and some may be "TINK" and "Konrad" users.
|
||||
/// </summary>
|
||||
/// <param name="p_oAccount">Account to filter with.</param>
|
||||
/// <param name="p_oSource">Groups to filter..</param>
|
||||
/// <returns>Filtered bike groups.</returns>
|
||||
public IEnumerable<string> DoFilter(IEnumerable<string> p_oSource = null)
|
||||
{
|
||||
return m_oAccount.DoFilter(p_oSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
TINKLib/Model/User/UsernamePasswordInvalidException.cs
Normal file
11
TINKLib/Model/User/UsernamePasswordInvalidException.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace TINK.Model.User
|
||||
{
|
||||
public class UsernamePasswordInvalidException : Exception
|
||||
{
|
||||
public UsernamePasswordInvalidException() : base("Benutzername und/ oder Passwort sind ungültig. Cookie ist leer.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
454
TINKLib/Model/WhatsNew.cs
Normal file
454
TINKLib/Model/WhatsNew.cs
Normal file
|
@ -0,0 +1,454 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TINK.MultilingualResources;
|
||||
|
||||
namespace TINK.Model
|
||||
{
|
||||
/// <summary> Holds information about TINKApp development. </summary>
|
||||
public class WhatsNew
|
||||
{
|
||||
private static readonly Version AGBMODIFIEDBUILD = new Version(3, 0, 131);
|
||||
|
||||
/// <summary>Change of of think App.</summary>
|
||||
private Dictionary<Version /* when change was introduced*/, string> WhatsNewMessages = new Dictionary<Version, string>
|
||||
{
|
||||
{
|
||||
new Version(3, 0, 0, 115),
|
||||
"Benutzeroberfläche verbessert.\r\n\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 120),
|
||||
"Verbesserung: Keine Fehler mehr beim schnellen Tippen.\r\n" +
|
||||
"Offlineanzeige Stationen/ Räderinfo.\r\n\r\n"
|
||||
},
|
||||
{
|
||||
AGBMODIFIEDBUILD,
|
||||
"Neue Seiten eingebaut\r\n" +
|
||||
"-zum erstmaligem Registrieren\r\n" +
|
||||
"-zur Verwaltung des Benutzerkontos\r\n" +
|
||||
"-zum Zurücksetzen des Passworts\r\n" +
|
||||
"\r\n" +
|
||||
"Anzeige Verbindungsstatus auf den Seiten\r\n" +
|
||||
"-Kartenansicht Fahrradstandorte\r\n" +
|
||||
"-Fahrräder an Station\r\n" +
|
||||
"-Meine Fahrräder\r\n\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 137),
|
||||
"Verschiedene kleine Verbesserungen und Korrekturen.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 141),
|
||||
"Erste I LOCK IT Unterstützung.\r\n" +
|
||||
"Erweiterte Optionen: Zwei Schlosssimulationen.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 142),
|
||||
"Sharee Server verfügbar.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 143),
|
||||
"Geolocation wird am Gerät abgefragt.\r\n" +
|
||||
"Erweiterte Optionen: Genauer Standort kann abgefragt werden, Standortsimulation verfügbar.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 144),
|
||||
"Diverse Fehler behoben.\r\n" +
|
||||
"Erweiterte Optionen: Texte Auswahlboxen für Copri-Server, Schlosssteuerung und Geolocation verständlicher gemacht.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 145),
|
||||
"Kleine Fehler behoben.\r\n" +
|
||||
"Erweiterte Option ein/ ausschalten \"Karte auf aktuelle Position ausrichten\" hinzugefügt.\r\n" +
|
||||
"Für gemietete Räder ausserhalb der Reichweite wird Knof \"Schloss suchen\" angezeigt."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 146),
|
||||
"Fehler behoben: Aktion Schloss schließen wird jetzt durchgeführt.\r\n" +
|
||||
"Benennung: \"Miete weiterführen\" -> \"Miete fortsetzen\".\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 147),
|
||||
"Erste prototypische Unterstützung des ILOCKIT-Schlosses.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 148),
|
||||
"Schloss-Guid wird an CORI bei Buchung übermittelt.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 149),
|
||||
"Schlösser mit neuem Advertisement-Name ISHAREIT+XXXXXXX unterstützt.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 150),
|
||||
"Verbesserung: Schlossstatus wird nach Öffnen/ Schließen abgefragt.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 151),
|
||||
"Verbesserungen:\r\n" +
|
||||
"Erweiterte Optionen: Auswahl, ob log-Dateien auf internem Speicher oder SD-Karte abgelegt werden, ist konfigurierbar.\r\n" +
|
||||
"Leistung: Suche nach Bluetooth Schlössern deutlich beschleunigt.\r\n" +
|
||||
"Kleine Textkorrekturen.\r\n." +
|
||||
"Fehlerbehebung: Schloss kann direkt nach Reservierung geöffnet werden.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 152),
|
||||
"Verbesserungen: Aufforderung zum Aktivieren von Bluetooth beim Öffnen der Seiten Meine Räder und Räder an Station implementiert.\r\n" +
|
||||
"Fehlerbehebung: Seite Meine Räder kann auch geöffnet werden, ohne dass Räder reserviert oder gemietet sein müssen.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 153),
|
||||
"Stabilität erhöht.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 154),
|
||||
"Stationen in Freiburg werden angezeigt.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 155),
|
||||
"Schlosssuche verbessert.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 156),
|
||||
"Abschalten von Sounds und Alarm für offene, reservierte Räder hinzugefügt.\r\n" +
|
||||
"Kleine Fehler behoben.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 157),
|
||||
"Versenden von Mail mit Diagnoseinformation funktioniert wieder.\r\n" +
|
||||
"Stationen werden nicht mehr fälschlicherweise ausgeblendet nach Verlassen von Einstellungsseite.\r\n" +
|
||||
"Absturz bei minimieren von App behoben.\r\n" +
|
||||
"Stabilität Bluetoothverbindung bei erstmaligem Verbinden verbessert.\r\n" +
|
||||
"Stabilität Bluetoothverbindung bei wiederholtem Verbinden verbessert.\r\n" +
|
||||
"Absturz bei Drehen von Smartdevice behoben.\r\n" +
|
||||
"Absturz bei minimieren von App behoben.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 158),
|
||||
"Bugfix: Auf Endgerät mit deutscher Sprache werden Texte wieder auf deutsch angezeigt.\r\n" +
|
||||
"Erweiterung: Räderinfo für TINK-Räder werden nur noch bei Anmeldung mit TINK-Konto angezeigt.\r\n" +
|
||||
"Erweiterung: Anwendergruppeninfotext \"TINK\" bzw. \"Konrad\" wird nur noch angezeigt, wenn Konto ein TINK- bzw. Konradrechte hat.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 159),
|
||||
"Bugfix: Asynchrone Bluetooth Aktualisierung für Android entfernt, da nicht unterstützt.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 162),
|
||||
"App umbenannt von TINKApp in sharee.bike.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 163),
|
||||
"Schlossstatus wird an COPRI übermittelt.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 164),
|
||||
"Wechsel Standard Lock-Umsetzung: GUID-Verbindungsaufbau wird statt Scan benutzt.\r\n" +
|
||||
"Datenquellen für \"Passwort vergessen\", \"Persönliche Daten Verwalten\", \"Datenschutz\" und \"AGB\" aktualisiert.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 165),
|
||||
"Menüstruktur überarbeitet.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 167),
|
||||
"Standardeinstellung geändert: Kartenansicht wird per Default auf aktuelle Position zentriert.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 168),
|
||||
"Konfigurierbaren Connect Timeout eingebaut.\r\n" +
|
||||
"Impressum, Radinfo und Tarifinfo wird von Server geladen.\r\n" +
|
||||
"Adressen für share.bike erweitert.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 169),
|
||||
"Verschiedene Fehler behoben.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 170),
|
||||
"Fehler behoben: nach Sequenz Rad Zurückgeben-Resverieren-Mieten ist wieder Verbindung zu Schloss möglich.\r\n" +
|
||||
"Zielplatform Android 10-Q"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 171),
|
||||
"Fehler behoben: nach Sequenz von Homescreen wieder App aktivieren wenn Meine Räder offen ist für nicht mehr zu Crash.\r\n" +
|
||||
"Fehlermeldung verbessert für den Fall, dass Bluetooth abgeschaltet ist."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 172),
|
||||
"Spezielle Fehler Schloss blockiert beim Öffnen/ Schließen und Fahrrad in Bewegung beim Schließen werden in Alert angezeigt.\r\n" +
|
||||
"Fehlerzustände werden detaillierter in Alerts angezeigt.\r\n" +
|
||||
"Überprüfung, ob von COPRI gelieferte GUID gültig ist für bekannte Schlösser.\r\n" +
|
||||
"Überprüfung, dass Seed nur einmalig verwendet werden.\r\n" +
|
||||
"Verschiedene kleinere Verbesserungen."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 173),
|
||||
"Fehlerzustände werden detaillierter in Alerts angezeigt.\r\n" +
|
||||
"Aktualisierung auf Android 10."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 174),
|
||||
"Fehlerkorrketur: GPS-Korrdianten werden länderinvariant übertragen. \r\n" +
|
||||
"Nutzer mit erweiterten Rechten: Alarm- und Soundeinstellungen können verwaltet werden."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 175),
|
||||
"Fehlerkorrektur: Nach Bluetooth-Wiederverbindung kann Schloss wieder geöffnet und geschlossen werden."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 176),
|
||||
"Wiederholen-/ Abbrechen-Schleife beim Verbinden mit Schlössern umgesetzt. Beim Wiederholen wird die Timeoutzeit jeweils verdoppelt bis zum Faktor vier.\r\n" +
|
||||
"Kein Neustart mehr notwendig nach Änderung der Timeouts."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 177),
|
||||
"Beim Schließen des Schlosses wird geänderter Zustand an COPRI übermittelt.\r\n" +
|
||||
"Beim Miete beenden, ohne dass unmittelbar voher das Schloss zu geschlossen wurde, werden keine Koordinaten an COPRI übermittelt.\r\n" +
|
||||
"Meldungen zu nichkritische Fehlern werden in der Statuszeile angezeigt. "
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 178),
|
||||
"Bei Fehlern bei Radrückgabe Fehlermeldung verbessert.\r\n" +
|
||||
"Activity Indicator (Sanduhr) eingebaut.\r\n" +
|
||||
"Timeout von 3 auf 5 Sekunden erhöht insbesonders für standard Lock-Umsetzung Live-Scan."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 179),
|
||||
"Verbesserte Fehlermeldungen bei Statusaktualisierung und Radrückgabe.\r\n" +
|
||||
"Fehlerbehebung: Alter Geoloationinformation wird korrekt übertragen.\r\n" +
|
||||
"Optimierung Benutzung Geolocationcache."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 180),
|
||||
"Akkufüllstand wir an COPRI übermittelt beim Schloss öffnen."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 190),
|
||||
"Erste Version für iOS."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 191),
|
||||
"Für Seiten \"Fahrradstandorte\", \"Meine Räder\" und \"Räder an Station\":\r\n" +
|
||||
"- Activity Indicator (Sanduhr) eingebaut\r\n" +
|
||||
"- Statusmeldungen eingebaut\r\n" +
|
||||
"Karte wird initial auf Verleistationen zentriert.\r\n" +
|
||||
"Geschwindikgeitsverbesserungen und Fehler behoben."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 192),
|
||||
"Erweiterung: Benutzerfreundliche Fehlermeldung für Szenario\r\n" +
|
||||
"-Rückgabe außerhalb von Station\r\n" +
|
||||
"-Rückgabe ohne GPS-Info\r\n" +
|
||||
"Standortabfrage bei Radrückgabe von bereits verschlossenem Rad, wenn Schloss in Bluetoothreichweite ist.\r\n" +
|
||||
"Fehlerbehebung: Passwort-Vergessen Funktionaliät wieder verfügbar.\r\n" +
|
||||
"Kontakt-Seite aktualisiert (Telefonnummer, Mail, ...)."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 193),
|
||||
"Erweiterte Benutzerrechte können selektiv akiviert werden.\r\n" +
|
||||
"Verschiedene Umbenennungen."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 194),
|
||||
"Fehlerkorrektur: Wenn kein Benutzer angemeldet ist werden nur noch öffentliche Stationen angezeigt.\r\n" +
|
||||
"Master-Detail Elemente werden in sharee.bike- Farbe angezeigt."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 195),
|
||||
"Fehlerkorrektur: Android action bar an sharee.bike- Farbenschema angepasst."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 196),
|
||||
"Fehlerkorrektur: Registrieren-Link korrigiert.\r\n" +
|
||||
"\"Kontakt\"-Seite überarbeitet."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 197),
|
||||
"Android: App ist nicht mehr verfügbar Geräte ohne BluetoothLE/ ohne GPS.\r\n" +
|
||||
"iOS:\r\n" +
|
||||
"- Bugfix: Nicht mehr benötigtes Recht \"Standort im Hintergrund\" entfernt.\r\n" +
|
||||
"- Schreibfehler behoben.\r\n" +
|
||||
"Fehlerhandling bei Benutzung von mehr als acht Geräte verbessert."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 198),
|
||||
"Fehlermeldungen angepasst.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 199),
|
||||
"Radbeschreibung auf sharee.bike angepasst.\r\n" +
|
||||
"iOS: Berechtigungsfehler behoben."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 200),
|
||||
"Titel von Seite Fahrradstandort verbessert.\r\n" +
|
||||
"Statusmeldungen verbessert."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 201),
|
||||
"iOS: Darstellung verbessert.\r\n" +
|
||||
"Weitere Teile der App englischsprachig verfügbar."
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 202),
|
||||
"Kleinere Verbesserrungen bezüglich Stabilität und Benutzbarkeit.\r\n"
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 203),
|
||||
AppResources.ChangeLog3_0_203
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 204),
|
||||
AppResources.ChangeLog3_0_204
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 205),
|
||||
AppResources.ChangeLog3_0_205
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 206),
|
||||
AppResources.ChangeLog3_0_206
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 207),
|
||||
AppResources.ChangeLog3_0_207
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 208),
|
||||
AppResources.ChangeLog3_0_208
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 209),
|
||||
AppResources.ChangeLog3_0_209
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 214),
|
||||
AppResources.ChangeLog3_0_214
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 215),
|
||||
AppResources.ChangeLog3_0_215
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 216),
|
||||
AppResources.ChangeLog3_0_216
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 217),
|
||||
AppResources.ChangeLog3_0_217
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 218),
|
||||
AppResources.ChangeLog3_0_218
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 219),
|
||||
AppResources.ChangeLog3_0_219
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 220),
|
||||
AppResources.ChangeLog3_0_220
|
||||
},
|
||||
{
|
||||
new Version(3, 0, 222),
|
||||
AppResources.ChangeLog3_0_222
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/// <summary> Manges the whats new information.</summary>
|
||||
/// <param name="currentVersion">Current version of the app.</param>
|
||||
/// <param name="shownInVersion">Null or version in which whats new dialog was shown last.</param>
|
||||
public WhatsNew(
|
||||
Version currentVersion,
|
||||
Version lastVersion,
|
||||
Version shownInVersion)
|
||||
{
|
||||
WasShownVersion = shownInVersion;
|
||||
LastVersion = lastVersion;
|
||||
CurrentVersion = currentVersion;
|
||||
}
|
||||
|
||||
/// <summary> Retruns a new WhatsNew object with property was shown set to true. </summary>
|
||||
/// <returns></returns>
|
||||
public WhatsNew SetWasShown()
|
||||
{
|
||||
return new WhatsNew(CurrentVersion, LastVersion, CurrentVersion);
|
||||
}
|
||||
|
||||
/// <summary> Holds the information in which version of the app the whats new dialog has been shown.</summary>
|
||||
private Version WasShownVersion { get; }
|
||||
|
||||
/// <summary> Holds the information in which version of the app the whats new dialog has been shown.</summary>
|
||||
private Version CurrentVersion { get; }
|
||||
|
||||
private Version LastVersion;
|
||||
|
||||
/// <summary> Holds information whether whats new page was already shown or not.</summary>
|
||||
public bool IsShowRequired
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CurrentVersion == null)
|
||||
{
|
||||
// Unexpected state detected.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (LastVersion == null)
|
||||
{
|
||||
// Initial install detected.
|
||||
return false;
|
||||
}
|
||||
|
||||
return (WasShownVersion ?? LastVersion) < CurrentVersion;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> True if info about modified agb has to be displayed. </summary>
|
||||
public bool IsShowAgbRequired => (WasShownVersion ?? AGBMODIFIEDBUILD) < AGBMODIFIEDBUILD;
|
||||
|
||||
/// <summary> Get the whats new text depening of version gap.</summary>
|
||||
public string WhatsNewText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CurrentVersion == null)
|
||||
{
|
||||
// Unexpected state detected.
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (LastVersion == null)
|
||||
{
|
||||
// Initial install detected. All is new.
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var effectiveWasShownVersion = WasShownVersion
|
||||
?? LastVersion; // Upgrade from version without whats new dialog detected.
|
||||
|
||||
var whatsNew = string.Empty;
|
||||
foreach (var l_oInfo in WhatsNewMessages)
|
||||
{
|
||||
if (effectiveWasShownVersion >= l_oInfo.Key)
|
||||
{
|
||||
// What new info for this version entry was already shown.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (l_oInfo.Key > CurrentVersion)
|
||||
{
|
||||
// This whats new info entry does not yet apply to current version.
|
||||
continue;
|
||||
}
|
||||
|
||||
whatsNew += $"<p><b>{l_oInfo.Key}</b><br/>{l_oInfo.Value}</p>";
|
||||
}
|
||||
|
||||
return whatsNew;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1594
TINKLib/MultilingualResources/AppResources.Designer.cs
generated
Normal file
1594
TINKLib/MultilingualResources/AppResources.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
546
TINKLib/MultilingualResources/AppResources.de.resx
Normal file
546
TINKLib/MultilingualResources/AppResources.de.resx
Normal file
|
@ -0,0 +1,546 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="ActionBookOrClose" xml:space="preserve">
|
||||
<value>Rad mieten oder Schloss schließen</value>
|
||||
</data>
|
||||
<data name="ActionCancelRequest" xml:space="preserve">
|
||||
<value>Reservierung aufheben</value>
|
||||
</data>
|
||||
<data name="ActionClose" xml:space="preserve">
|
||||
<value>Schloss schließen</value>
|
||||
</data>
|
||||
<data name="ActionCloseAndReturn" xml:space="preserve">
|
||||
<value>Schloss schließen & Miete beenden</value>
|
||||
</data>
|
||||
<data name="ActionOpen" xml:space="preserve">
|
||||
<value>Schloss öffnen</value>
|
||||
</data>
|
||||
<data name="ActionOpenAndBook" xml:space="preserve">
|
||||
<value>Schloss öffnen & Rad mieten</value>
|
||||
</data>
|
||||
<data name="ActionOpenAndPause" xml:space="preserve">
|
||||
<value>Schloss öffnen & Miete fortsetzen</value>
|
||||
</data>
|
||||
<data name="ActionRequest" xml:space="preserve">
|
||||
<value>Rad reservieren</value>
|
||||
</data>
|
||||
<data name="ActionReturn" xml:space="preserve">
|
||||
<value>Miete beenden</value>
|
||||
</data>
|
||||
<data name="MarkingMapPage" xml:space="preserve">
|
||||
<value>Fahrradstandorte</value>
|
||||
</data>
|
||||
<data name="MessageLoginWelcome" xml:space="preserve">
|
||||
<value>Benutzer {0} erfolgreich angemeldet.</value>
|
||||
</data>
|
||||
<data name="MessageLoginWelcomeTitle" xml:space="preserve">
|
||||
<value>Willkommen!</value>
|
||||
</data>
|
||||
<data name="MessageLoginWelcomeTitleGroup" xml:space="preserve">
|
||||
<value>Wilkommen bei {0}!</value>
|
||||
</data>
|
||||
<data name="MarkingLoggedInStateInfoLoggedIn" xml:space="preserve">
|
||||
<value>Angemeldet als {0}.</value>
|
||||
</data>
|
||||
<data name="MarkingLoggedInStateInfoLoggedInGroup" xml:space="preserve">
|
||||
<value>Angemeldet als {0} bei {1}.</value>
|
||||
</data>
|
||||
<data name="MarkingLoggedInStateInfoNotLoggedIn" xml:space="preserve">
|
||||
<value>Kein Benutzer angemeldet.</value>
|
||||
</data>
|
||||
<data name="MessageAppVersionIsOutdated" xml:space="preserve">
|
||||
<value>Diese version der {0} App ist veraltet. Bitte auf aktuelle Version aktualisieren.</value>
|
||||
</data>
|
||||
<data name="QuestionSupportmailSubject" xml:space="preserve">
|
||||
<value>Betrifft die Anfrage/ Anmerkung die {0}-App oder ein allgemeines Thema?</value>
|
||||
</data>
|
||||
<data name="QuestionSupportmailAnswerApp" xml:space="preserve">
|
||||
<value>{0}-App Anfrage</value>
|
||||
</data>
|
||||
<data name="QuestionSupportmailAnswerOperator" xml:space="preserve">
|
||||
<value>{0} Anfrage</value>
|
||||
</data>
|
||||
<data name="MarkingAbout" xml:space="preserve">
|
||||
<value>Über {0}</value>
|
||||
</data>
|
||||
<data name="MarkingAccount" xml:space="preserve">
|
||||
<value>Konto</value>
|
||||
</data>
|
||||
<data name="MarkingFeedbackAndContact" xml:space="preserve">
|
||||
<value>Kontakt</value>
|
||||
</data>
|
||||
<data name="MarkingLogin" xml:space="preserve">
|
||||
<value>Anmelden</value>
|
||||
</data>
|
||||
<data name="MarkingMyBikes" xml:space="preserve">
|
||||
<value>Meine Räder</value>
|
||||
</data>
|
||||
<data name="MarkingFeesAndBikes" xml:space="preserve">
|
||||
<value>Bedienung</value>
|
||||
</data>
|
||||
<data name="MarkingSettings" xml:space="preserve">
|
||||
<value>Einstellungen</value>
|
||||
</data>
|
||||
<data name="MarkingTabBikes" xml:space="preserve">
|
||||
<value>Bedienung</value>
|
||||
</data>
|
||||
<data name="MarkingTabFees" xml:space="preserve">
|
||||
<value>Tarife</value>
|
||||
</data>
|
||||
<data name="ErrorOpenLockMessage" xml:space="preserve">
|
||||
<value>Schloss ist blockiert. Bitte Ursache von Blockierung beheben und Vorgang wiederholen.</value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockBoldBlockedMessage" xml:space="preserve">
|
||||
<value>Schloss ist blockiert. Bitte sicherstellen, dass keine Speiche oder ein anderer Gegenstand das Schloss blockiert und Vorgang wiederholen.</value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockMovingMessage" xml:space="preserve">
|
||||
<value>Schloss kann erst geschlossen werden, wenn Rad nicht mehr bewegt wird. Bitte Rad abstellen und Vorgang wiederholen.</value>
|
||||
</data>
|
||||
<data name="ErrorBookedSearchMessage" xml:space="preserve">
|
||||
<value>Schloss des gemieteten Rads kann nicht gefunden werden.</value>
|
||||
</data>
|
||||
<data name="ErrorReservedSearchMessage" xml:space="preserve">
|
||||
<value>Schloss des reservierten Rads kann nicht gefunden werden.</value>
|
||||
</data>
|
||||
<data name="ActivityTextBikesAtStationGetBikes" xml:space="preserve">
|
||||
<value>Lade Räder an Station...</value>
|
||||
</data>
|
||||
<data name="ActivityTextMyBikesLoadingBikes" xml:space="preserve">
|
||||
<value>Lade meine Räder...</value>
|
||||
</data>
|
||||
<data name="ActivityTextSearchBikes" xml:space="preserve">
|
||||
<value>Suche Schlösser...</value>
|
||||
</data>
|
||||
<data name="ActivityTextMyBikesCheckBluetoothState" xml:space="preserve">
|
||||
<value>Prüfe Berechtigungen...</value>
|
||||
</data>
|
||||
<data name="ActivityTextCenterMap" xml:space="preserve">
|
||||
<value>Zentriere Karte...</value>
|
||||
</data>
|
||||
<data name="ActivityTextMapLoadingStationsAndBikes" xml:space="preserve">
|
||||
<value>Lade Stationen und Räder...</value>
|
||||
</data>
|
||||
<data name="ErrorReturnBikeNotAtStationMessage" xml:space="preserve">
|
||||
<value>Rückgabe ausserhalb von Station nicht möglich. Entfernung zur Station {0} ist {1} m.</value>
|
||||
</data>
|
||||
<data name="ErrorReturnBikeNotAtStationTitle" xml:space="preserve">
|
||||
<value>Fehler bei Radrückgabe!</value>
|
||||
</data>
|
||||
<data name="ErrorReturnBikeLockClosedNoGPSMessage" xml:space="preserve">
|
||||
<value>Fahrradrückgabe an unbekanntem Standort nicht möglich.
|
||||
Eine Radrückgabe ist möglich, wenn
|
||||
- beim Schliessen des Schlosses Standortinformation verfügbar ist
|
||||
- beim Drücken von "Rad zurückgeben" das Rad in Reichweite ist und Standortinformation verfügbar ist.</value>
|
||||
</data>
|
||||
<data name="ErrorReturnBikeLockOpenNoGPSMessage" xml:space="preserve">
|
||||
<value>Fahrradrückgabe an unbekanntem Standort nicht möglich.
|
||||
Eine Radrückgabe ist nur möglich, wenn das Rad in Reichweite ist und Standortinformation verfügbar ist.</value>
|
||||
</data>
|
||||
<data name="ErrorSupportmailCreateAttachment" xml:space="preserve">
|
||||
<value>Mailanhang konnte nich erzeugt werden.</value>
|
||||
</data>
|
||||
<data name="ErrorSupportmailMailingFailed" xml:space="preserve">
|
||||
<value>Mailapp konnte nicht geöffnet werden.</value>
|
||||
</data>
|
||||
<data name="ErrorSupportmailPhoningFailed" xml:space="preserve">
|
||||
<value>Telefonapp konnte nicht geöffnet werden.</value>
|
||||
</data>
|
||||
<data name="MessageAnswerOk" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
</data>
|
||||
<data name="MessageContactMail" xml:space="preserve">
|
||||
<value>Fragen? Hinweise? Kritik?</value>
|
||||
</data>
|
||||
<data name="MessagePhoneMail" xml:space="preserve">
|
||||
<value>Eilige Frage rund um {0}? (Montag-Freitag: 10:00 18:00)</value>
|
||||
</data>
|
||||
<data name="MessageRateMail" xml:space="preserve">
|
||||
<value>Gefällt die {0}-App?</value>
|
||||
</data>
|
||||
<data name="MessageWaring" xml:space="preserve">
|
||||
<value>Warnung</value>
|
||||
</data>
|
||||
<data name="QuestionAnswerNo" xml:space="preserve">
|
||||
<value>Nein</value>
|
||||
</data>
|
||||
<data name="QuestionAnswerYes" xml:space="preserve">
|
||||
<value>Ja</value>
|
||||
</data>
|
||||
<data name="QuestionSupportmailAttachment" xml:space="preserve">
|
||||
<value>Soll der Mail eine Datei mit Diagnoseinformationen angehängt werden?</value>
|
||||
</data>
|
||||
<data name="QuestionTitle" xml:space="preserve">
|
||||
<value>Frage</value>
|
||||
</data>
|
||||
<data name="MessageMapPageErrorAuthcookieUndefined" xml:space="preserve">
|
||||
<value>Sitzung ist abgelaufen.
|
||||
Entweder es sind mehr als 8 Geräte in Benutzung oder das Konto ist nicht mehr gültig.
|
||||
Die Nutzung der App ist auf maximal 8 Geräten pro Konto möglich.
|
||||
Bitte erneut in App anmelden. Sollte dies fehlschlagen bitte auf Website prüfen, ob das Konto noch gültig ist.</value>
|
||||
</data>
|
||||
<data name="MarkingBikesAtStationTitle" xml:space="preserve">
|
||||
<value>Fahrradstandort {0}</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredCodeMaxReservationTime" xml:space="preserve">
|
||||
<value>Code ist {0}, max. Reservierungszeit von {1} Min. abgelaufen.
|
||||
</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredCodeRemaining" xml:space="preserve">
|
||||
<value>Code ist {0}, noch {1} Minuten reserviert.
|
||||
</value>
|
||||
</data>
|
||||
<data name="StatusTextBooked" xml:space="preserve">
|
||||
<value>Rad ist gemietet.</value>
|
||||
</data>
|
||||
<data name="StatusTextBookedSince" xml:space="preserve">
|
||||
<value>Gemietet seit {0}.</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredRemaining" xml:space="preserve">
|
||||
<value>Noch {0} Minuten reserviert.</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredMaximumReservationTime" xml:space="preserve">
|
||||
<value>Max. Reservierungszeit von {0} Min. abgelaufen.</value>
|
||||
</data>
|
||||
<data name="StatusTextBookedCodeSince" xml:space="preserve">
|
||||
<value>Code ist {0}, gemietet seit {1}.</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredLocationMaxReservationTime" xml:space="preserve">
|
||||
<value>Standort {0}, max. Reservierungszeit von {1} Min. abgelaufen.
|
||||
</value>
|
||||
</data>
|
||||
<data name="StatusTextAvailable" xml:space="preserve">
|
||||
<value>Frei.</value>
|
||||
</data>
|
||||
<data name="StatusTextBookedCodeLocationSince" xml:space="preserve">
|
||||
<value>Code {0}, Standort {1}, gemietet seit {2}.
|
||||
</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredCodeLocationMaxReservationTime" xml:space="preserve">
|
||||
<value>Code ist {0}, Standort {1}, max. Reservierungszeit von {2} Min. abgelaufen.
|
||||
</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredCodeLocationReservationTime" xml:space="preserve">
|
||||
<value>Code ist {0}, Standort {1}, noch {2} Minuten reserviert.
|
||||
</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredLocationReservationTime" xml:space="preserve">
|
||||
<value>Standort {0}, noch {1} Minuten reserviert.
|
||||
</value>
|
||||
</data>
|
||||
<data name="ActionLoginLogin" xml:space="preserve">
|
||||
<value>Anmelden</value>
|
||||
</data>
|
||||
<data name="ActionLoginPasswordForgotten" xml:space="preserve">
|
||||
<value>Passwort vergessen?</value>
|
||||
</data>
|
||||
<data name="ActionLoginRegister" xml:space="preserve">
|
||||
<value>Registrieren</value>
|
||||
</data>
|
||||
<data name="MarkingLoginEmailAddressLabel" xml:space="preserve">
|
||||
<value>E-Mail Adresse</value>
|
||||
</data>
|
||||
<data name="MarkingLoginEmailAddressPlaceholder" xml:space="preserve">
|
||||
<value>E-Mail Adresse</value>
|
||||
</data>
|
||||
<data name="MarkingLoginPasswordLabel" xml:space="preserve">
|
||||
<value>Passwort, Mindestlänge 8 Zeichen</value>
|
||||
</data>
|
||||
<data name="MarkingLoginPasswordPlaceholder" xml:space="preserve">
|
||||
<value>Passwort</value>
|
||||
</data>
|
||||
<data name="ActionContactRate" xml:space="preserve">
|
||||
<value>Bewertung abgeben</value>
|
||||
</data>
|
||||
<data name="MarkingLoginInstructions" xml:space="preserve">
|
||||
<value>Anleitung TINK Räder</value>
|
||||
</data>
|
||||
<data name="MarkingLoginInstructionsTinkKonradMessage" xml:space="preserve">
|
||||
<value>Falls Sie bereits einen "konrad" oder "TINK" Account besitzen, können Sie mit Ihren bestehendem Account die Nutzung beider Mietradsysteme einstellen! Einfach dazu die entsprechende AGB bestätigen.</value>
|
||||
</data>
|
||||
<data name="MarkingLoginInstructionsTinkKonradTitle" xml:space="preserve">
|
||||
<value>Zur Information!</value>
|
||||
</data>
|
||||
<data name="MessageLoginConnectionErrorMessage" xml:space="preserve">
|
||||
<value>Anmeldungskeks darf nicht leer sein. {0}</value>
|
||||
</data>
|
||||
<data name="MessageLoginConnectionErrorTitle" xml:space="preserve">
|
||||
<value>Verbindungsfehler beim Registrieren!</value>
|
||||
</data>
|
||||
<data name="MessageLoginErrorTitle" xml:space="preserve">
|
||||
<value>Fehler bei der Anmeldung!</value>
|
||||
</data>
|
||||
<data name="MessageLoginRecoverPassword" xml:space="preserve">
|
||||
<value>Bitte mit dem Internet verbinden zum Wiederherstellen des Passworts.</value>
|
||||
</data>
|
||||
<data name="MessageLoginRegisterNoNet" xml:space="preserve">
|
||||
<value>Bitte mit dem Internet verbinden zum Registrieren.</value>
|
||||
</data>
|
||||
<data name="MessageTitleHint" xml:space="preserve">
|
||||
<value>Hinweis</value>
|
||||
</data>
|
||||
<data name="ErrorOpenLockStillClosedMessage" xml:space="preserve">
|
||||
<value>Nach Versuch Schloss zu öffnen wird Status geschlossen zurückgemeldet.</value>
|
||||
</data>
|
||||
<data name="ErrorOpenLockOutOfReadMessage" xml:space="preserve">
|
||||
<value>Schloss kann erst geöffnet werden, wenn Rad in der Nähe ist.</value>
|
||||
</data>
|
||||
<data name="ErrorOpenLockTitle" xml:space="preserve">
|
||||
<value>Schloss kann nicht geöffnet werden!</value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockOutOfReachMessage" xml:space="preserve">
|
||||
<value>Schloss kann erst geschlossen werden, wenn das Rad in der Nähe ist. </value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockOutOfReachStateReservedMessage" xml:space="preserve">
|
||||
<value>Schloss kann erst geschlossen werden, wenn das Rad in der Nähe ist.
|
||||
Bitte Schließen nochmals versuchen oder Rad dem Support melden!</value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockTitle" xml:space="preserve">
|
||||
<value>Schloss kann nicht geschlossen werden!</value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockStillOpenMessage" xml:space="preserve">
|
||||
<value>Nach Versuch Schloss zu schließen wird Status geöffnet zurückgemeldet.</value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockUnexpectedStateMessage" xml:space="preserve">
|
||||
<value>Schloss meldet Status "{0}".</value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockUnkErrorMessage" xml:space="preserve">
|
||||
<value>Bitte schließen nochmals versuchen oder Rad dem Support melden!
|
||||
{0}</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementLocationPermission" xml:space="preserve">
|
||||
<value>Bitte Standortfreigabe erlauben, damit Fahrradschloss/ Schlösser verwaltet werden können.</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementLocationPermissionOpenDialog" xml:space="preserve">
|
||||
<value>Bitte Standortfreigabe erlauben, damit Fahrradschloss/ Schlösser verwaltet werden können.
|
||||
Freigabedialog öffen?</value>
|
||||
</data>
|
||||
<data name="MessageCenterMapLocationPermissionOpenDialog" xml:space="preserve">
|
||||
<value>Bitte Standortfreigabe erlauben, damit Karte zentriert werden werden kann.
|
||||
Freigabedialog öffen?</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementLocationActivation" xml:space="preserve">
|
||||
<value>Bitte Standort aktivieren, damit Fahrradschloss gefunden werden kann!</value>
|
||||
</data>
|
||||
<data name="MessageAnswerNo" xml:space="preserve">
|
||||
<value>Nein</value>
|
||||
</data>
|
||||
<data name="MessageAnswerYes" xml:space="preserve">
|
||||
<value>Ja</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementBluetoothActivation" xml:space="preserve">
|
||||
<value>Bitte Bluetooth aktivieren, damit Fahrradschloss/ Schlösser verwaltet werden können.</value>
|
||||
</data>
|
||||
<data name="ActionSearchLock" xml:space="preserve">
|
||||
<value>Schloss suchen</value>
|
||||
</data>
|
||||
<data name="ActivityTextOneMomentPlease" xml:space="preserve">
|
||||
<value>Einen Moment bitte...</value>
|
||||
</data>
|
||||
<data name="ActivityTextOpeningLock" xml:space="preserve">
|
||||
<value>Öffne Schloss...</value>
|
||||
</data>
|
||||
<data name="ActivityTextStartingUpdater" xml:space="preserve">
|
||||
<value>Starte Aktualisierung...</value>
|
||||
</data>
|
||||
<data name="ActivityTextStartingUpdatingLockingState" xml:space="preserve">
|
||||
<value>Aktualisiere Schlossstatus...</value>
|
||||
</data>
|
||||
<data name="ActivityTextReadingChargingLevel" xml:space="preserve">
|
||||
<value>Lese Akkustatus...</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorStatusUpdateingLockstate" xml:space="preserve">
|
||||
<value>Statusfehler beim Aktualisieren des Schlossstatusses.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorConnectionUpdateingLockstate" xml:space="preserve">
|
||||
<value>Verbingungsfehler beim Aktualisieren des Schlossstatusses.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorNoWebUpdateingLockstate" xml:space="preserve">
|
||||
<value>Kein Netz beim Aktualisieren des Schlossstatusses.</value>
|
||||
</data>
|
||||
<data name="ActivityTextClosingLock" xml:space="preserve">
|
||||
<value>Schließe Schloss...</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_203" xml:space="preserve">
|
||||
<value>Aktualisierrt auf aktuelle Schloss-Firmware.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_204" xml:space="preserve">
|
||||
<value>Bluetooth Kommunikation verbessert.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_205" xml:space="preserve">
|
||||
<value>Stationssymbole verbessert.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_206" xml:space="preserve">
|
||||
<value>Bluetooth- und Geolocation-Funktionalität verbessert.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_207" xml:space="preserve">
|
||||
<value>Kleinere Fehlerbehebungen.
|
||||
Software Pakete aktualisiert.
|
||||
Zielplatform Android 11.</value>
|
||||
</data>
|
||||
<data name="MessageOpenLockAndBookeBike" xml:space="preserve">
|
||||
<value>Fahrrad {0} mieten und Schloss öffnen?</value>
|
||||
</data>
|
||||
<data name="ActivityTextReservingBike" xml:space="preserve">
|
||||
<value>Reserviere Rad...</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorReadingChargingLevelGeneral" xml:space="preserve">
|
||||
<value>Akkustatus kann nicht gelesen werden.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorReadingChargingLevelOutOfReach" xml:space="preserve">
|
||||
<value>Akkustatus kann erst gelesen werden, wenn Rad in der Nähe ist.</value>
|
||||
</data>
|
||||
<data name="ActivityTextRentingBike" xml:space="preserve">
|
||||
<value>Miete Rad...</value>
|
||||
</data>
|
||||
<data name="MessageRentingBikeErrorConnectionTitle" xml:space="preserve">
|
||||
<value>Verbingungsfehler beim Mieten des Rads!</value>
|
||||
</data>
|
||||
<data name="MessageRentingBikeErrorGeneralTitle" xml:space="preserve">
|
||||
<value>Fehler beim Mieten des Rads!</value>
|
||||
</data>
|
||||
<data name="MessageRentingBikeErrorTooManyReservationsRentals" xml:space="preserve">
|
||||
<value>Eine Miete des Rads {0} wurde abgelehnt, weil die maximal erlaubte Anzahl von {1} Reservierungen/ Buchungen bereits getätigt wurden.</value>
|
||||
</data>
|
||||
<data name="MessageReservationBikeErrorTooManyReservationsRentals" xml:space="preserve">
|
||||
<value>Eine Reservierung des Rads {0} wurde abgelehnt, weil die maximal erlaubte Anzahl von {1} Reservierungen/ Buchungen bereits getätigt wurden.</value>
|
||||
</data>
|
||||
<data name="MessageErrorLockIsClosedThreeLines" xml:space="preserve">
|
||||
<value>Achtung: Schloss wird geschlossen!
|
||||
{0}
|
||||
{1}</value>
|
||||
</data>
|
||||
<data name="MessageErrorLockIsClosedTwoLines" xml:space="preserve">
|
||||
<value>Achtung: Schloss wird geschlossen!
|
||||
{0}</value>
|
||||
</data>
|
||||
<data name="ExceptionTextRentingBikeFailedGeneral" xml:space="preserve">
|
||||
<value>Die Miete des Fahrads Nr. {0} ist fehlgeschlagen.</value>
|
||||
</data>
|
||||
<data name="ExceptionTextRentingBikeFailedUnavailalbe" xml:space="preserve">
|
||||
<value>Die Miete des Rads Nr. {0} ist fehlgeschlagen, das Rad ist momentan nicht erreichbar.</value>
|
||||
</data>
|
||||
<data name="ExceptionTextReservationBikeFailedGeneral" xml:space="preserve">
|
||||
<value>Die Reservierung des Fahrads Nr. {0} ist fehlgeschlagen.</value>
|
||||
</data>
|
||||
<data name="ExceptionTextReservationBikeFailedUnavailalbe" xml:space="preserve">
|
||||
<value>Die Reservierung des Rads Nr. {0} ist fehlgeschlagen, das Rad ist momentan nicht erreichbar.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_208" xml:space="preserve">
|
||||
<value>Kleinere Fehlerbehebungen.</value>
|
||||
</data>
|
||||
<data name="ActivityTextDisconnectingLock" xml:space="preserve">
|
||||
<value>Trenne Schloss...</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorDisconnect" xml:space="preserve">
|
||||
<value>Fehler beim Trennen...</value>
|
||||
</data>
|
||||
<data name="MarkingBikeInfoErrorStateDisposableClosedDetected" xml:space="preserve">
|
||||
<value>Ungülitiger Status von Rad {0} erkannt.
|
||||
Ein nicht reserviertes oder gemietetes Rad sollte immer getrennt sein .
|
||||
Bitte App neu starten um Rad Infos zu bekommen.</value>
|
||||
</data>
|
||||
<data name="MarkingBikeInfoErrorStateUnknownDetected" xml:space="preserve">
|
||||
<value>Ungültiger Mietstatus von Rad {0} erkannt.
|
||||
|
||||
</value>
|
||||
</data>
|
||||
<data name="ActivityTextCancelingReservation" xml:space="preserve">
|
||||
<value>Reservierung aufheben...</value>
|
||||
</data>
|
||||
<data name="QuestionCancelReservation" xml:space="preserve">
|
||||
<value>Reservierung für Fahrrad {0} aufheben?</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_209" xml:space="preserve">
|
||||
<value>Kleinere Fehlerbehebung: Verbindung wird getrennt, sobald Rad verfügbar ist.</value>
|
||||
</data>
|
||||
<data name="QuestionReserveBike" xml:space="preserve">
|
||||
<value>Fahrrad {0} kostenlos für {1} Min. reservieren?</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorDeserializationException" xml:space="preserve">
|
||||
<value>Verbindungsfehler: Deserialisierung fehlgeschlagen.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorException" xml:space="preserve">
|
||||
<value>Verbindung unterbrochen.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorInvalidResponseException" xml:space="preserve">
|
||||
<value>Verbindungsfehler, ungülige Serverantwort.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorWebConnectFailureException" xml:space="preserve">
|
||||
<value>Verbindung unterbrochen, Server nicht erreichbar.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorWebException" xml:space="preserve">
|
||||
<value>Verbindungsfehler. Code: {0}.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorWebForbiddenException" xml:space="preserve">
|
||||
<value>Verbindung unterbrochen, Server beschäftigt.</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementTariffDescriptionAboEuroPerMonth" xml:space="preserve">
|
||||
<value>Abo-Preis</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementTariffDescriptionFeeEuroPerHour" xml:space="preserve">
|
||||
<value>Mietgebühren</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementTariffDescriptionFreeTimePerSession" xml:space="preserve">
|
||||
<value>Gratis Nutzung</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementTariffDescriptionMaxFeeEuroPerDay" xml:space="preserve">
|
||||
<value>Max. Gebühr</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementTariffDescriptionEuroPerHour" xml:space="preserve">
|
||||
<value>€/Std.</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementTariffDescriptionHour" xml:space="preserve">
|
||||
<value>Std./Tag</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementTariffDescriptionTariffHeader" xml:space="preserve">
|
||||
<value>Tarif {0}, Nr. {1}</value>
|
||||
</data>
|
||||
<data name="ActivityTextQuerryServer" xml:space="preserve">
|
||||
<value>Anfrage Server...</value>
|
||||
</data>
|
||||
<data name="ActivityTextSearchingLock" xml:space="preserve">
|
||||
<value>Suche Schloss...</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_214" xml:space="preserve">
|
||||
<value>Mehrbetreiber Unterstützung.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_215" xml:space="preserve">
|
||||
<value>Layout des "Whats New"-Dialos verbessert :-)</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementMaxFeeEuroPerDay" xml:space="preserve">
|
||||
<value>€/Tag</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_216" xml:space="preserve">
|
||||
<value>Oberflächenlayout verbessert.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_217" xml:space="preserve">
|
||||
<value>Pakete aktualisiert.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_218" xml:space="preserve">
|
||||
<value>Kleine Verbesserungen.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_219" xml:space="preserve">
|
||||
<value>Icons zum Flyout-Menü hinzugefügt.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_220" xml:space="preserve">
|
||||
<value>Oberflächenlayout verbessert.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_221" xml:space="preserve">
|
||||
<value>Oberflächenlayout verbessert.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_222" xml:space="preserve">
|
||||
<value>Kleine Fehlerbehebungen und Verbesserungen.</value>
|
||||
</data>
|
||||
</root>
|
643
TINKLib/MultilingualResources/AppResources.resx
Normal file
643
TINKLib/MultilingualResources/AppResources.resx
Normal file
|
@ -0,0 +1,643 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="ActionBookOrClose" xml:space="preserve">
|
||||
<value>Rent bike or close lock</value>
|
||||
</data>
|
||||
<data name="ActionCancelRequest" xml:space="preserve">
|
||||
<value>Cancel bike reservation</value>
|
||||
</data>
|
||||
<data name="ActionClose" xml:space="preserve">
|
||||
<value>Close lock</value>
|
||||
</data>
|
||||
<data name="ActionCloseAndReturn" xml:space="preserve">
|
||||
<value>Close lock & return bike</value>
|
||||
</data>
|
||||
<data name="ActionOpen" xml:space="preserve">
|
||||
<value>Open lock</value>
|
||||
</data>
|
||||
<data name="ActionOpenAndBook" xml:space="preserve">
|
||||
<value>Open lock & rent bike</value>
|
||||
</data>
|
||||
<data name="ActionOpenAndPause" xml:space="preserve">
|
||||
<value>Open lock & continue renting</value>
|
||||
</data>
|
||||
<data name="ActionRequest" xml:space="preserve">
|
||||
<value>Reserve bike</value>
|
||||
</data>
|
||||
<data name="ActionReturn" xml:space="preserve">
|
||||
<value>Return bike</value>
|
||||
</data>
|
||||
<data name="ActivityTextMapLoadingStationsAndBikes" xml:space="preserve">
|
||||
<value>Loading Stations and Bikes...</value>
|
||||
</data>
|
||||
<data name="ErrorBookedSearchMessage" xml:space="preserve">
|
||||
<value>Lock of rented bike can not be found.</value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockBoldBlockedMessage" xml:space="preserve">
|
||||
<value>Lock is blocked. Please ensure that no spoke or any other obstacle prevents the lock from closing and try again.</value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockMovingMessage" xml:space="preserve">
|
||||
<value>Lock can only be closed if bike is not moving. Please park bike and try again.</value>
|
||||
</data>
|
||||
<data name="ErrorOpenLockMessage" xml:space="preserve">
|
||||
<value>Lock is blocked. Please ensure that no obstacle prevents lock from opening and try again.</value>
|
||||
</data>
|
||||
<data name="ErrorReservedSearchMessage" xml:space="preserve">
|
||||
<value>Lock of reserved bike can not be found.</value>
|
||||
</data>
|
||||
<data name="ErrorReturnBikeLockClosedNoGPSMessage" xml:space="preserve">
|
||||
<value>Returning bike at an unknown location is not possible.
|
||||
Bike can be returned if
|
||||
- location information is available when closing lock
|
||||
- bike is in reach and location information is available when pressing button "Return bike"</value>
|
||||
</data>
|
||||
<data name="ErrorReturnBikeLockOpenNoGPSMessage" xml:space="preserve">
|
||||
<value>Returning bike at an unknown location is not possible.
|
||||
Bike can only be returned if bike is in reach and location information is available.</value>
|
||||
</data>
|
||||
<data name="ErrorReturnBikeNotAtStationMessage" xml:space="preserve">
|
||||
<value>Returning bike outside of station is not possible. Distance to station {0} is {1} m.</value>
|
||||
</data>
|
||||
<data name="ErrorReturnBikeNotAtStationTitle" xml:space="preserve">
|
||||
<value>Error returning bike!</value>
|
||||
</data>
|
||||
<data name="ErrorSupportmailCreateAttachment" xml:space="preserve">
|
||||
<value>Attachment could not be created.</value>
|
||||
</data>
|
||||
<data name="ErrorSupportmailMailingFailed" xml:space="preserve">
|
||||
<value>Opening mail app failed.</value>
|
||||
</data>
|
||||
<data name="ErrorSupportmailPhoningFailed" xml:space="preserve">
|
||||
<value>Opening phone app failed.</value>
|
||||
</data>
|
||||
<data name="MarkingAbout" xml:space="preserve">
|
||||
<value>About {0}</value>
|
||||
</data>
|
||||
<data name="MarkingAccount" xml:space="preserve">
|
||||
<value>Account</value>
|
||||
</data>
|
||||
<data name="MarkingBikesAtStationTitle" xml:space="preserve">
|
||||
<value>Bike Location {0}</value>
|
||||
</data>
|
||||
<data name="MarkingFeedbackAndContact" xml:space="preserve">
|
||||
<value>Contact</value>
|
||||
</data>
|
||||
<data name="MarkingFeesAndBikes" xml:space="preserve">
|
||||
<value>Instructions</value>
|
||||
</data>
|
||||
<data name="MarkingLoggedInStateInfoLoggedIn" xml:space="preserve">
|
||||
<value>Logged in as {0}.</value>
|
||||
</data>
|
||||
<data name="MarkingLoggedInStateInfoLoggedInGroup" xml:space="preserve">
|
||||
<value>Logged in as {0} at {1}.</value>
|
||||
</data>
|
||||
<data name="MarkingLoggedInStateInfoNotLoggedIn" xml:space="preserve">
|
||||
<value>No user logged in.</value>
|
||||
</data>
|
||||
<data name="MarkingLogin" xml:space="preserve">
|
||||
<value>Login</value>
|
||||
</data>
|
||||
<data name="MarkingMapPage" xml:space="preserve">
|
||||
<value>Bike Locations</value>
|
||||
</data>
|
||||
<data name="MarkingMyBikes" xml:space="preserve">
|
||||
<value>My Bikes</value>
|
||||
</data>
|
||||
<data name="MarkingSettings" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="MarkingTabBikes" xml:space="preserve">
|
||||
<value>Instructions</value>
|
||||
</data>
|
||||
<data name="MarkingTabFees" xml:space="preserve">
|
||||
<value>Pricing</value>
|
||||
</data>
|
||||
<data name="MessageAnswerOk" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
</data>
|
||||
<data name="MessageAppVersionIsOutdated" xml:space="preserve">
|
||||
<value>This version of the {0} App is outdated. Please update to the latest version.</value>
|
||||
</data>
|
||||
<data name="MessageContactMail" xml:space="preserve">
|
||||
<value>Questions? Remarks? Criticism?</value>
|
||||
</data>
|
||||
<data name="MessageLoginWelcome" xml:space="preserve">
|
||||
<value>User {0} successfully logged in.</value>
|
||||
</data>
|
||||
<data name="MessageLoginWelcomeTitle" xml:space="preserve">
|
||||
<value>Welcome!</value>
|
||||
</data>
|
||||
<data name="MessageLoginWelcomeTitleGroup" xml:space="preserve">
|
||||
<value>Welcome to {0}!</value>
|
||||
</data>
|
||||
<data name="MessageMapPageErrorAuthcookieUndefined" xml:space="preserve">
|
||||
<value>Session has expired.
|
||||
Either there are more than 8 devices in use or the user's account is no longer valid.
|
||||
Use of app is restricted to maximu 8 devices per account.
|
||||
Please login to app once again. In case this fails please check on website if the account is still valid.</value>
|
||||
</data>
|
||||
<data name="MessagePhoneMail" xml:space="preserve">
|
||||
<value>Urgent question related to {0}? (Monday-Friday: 10:00 18:00)</value>
|
||||
</data>
|
||||
<data name="MessageRateMail" xml:space="preserve">
|
||||
<value>Are you enjoying the {0}-App?</value>
|
||||
</data>
|
||||
<data name="MessageWaring" xml:space="preserve">
|
||||
<value>Warning</value>
|
||||
</data>
|
||||
<data name="QuestionAnswerNo" xml:space="preserve">
|
||||
<value>No</value>
|
||||
</data>
|
||||
<data name="QuestionAnswerYes" xml:space="preserve">
|
||||
<value>Yes</value>
|
||||
</data>
|
||||
<data name="QuestionSupportmailAnswerApp" xml:space="preserve">
|
||||
<value>{0} app request</value>
|
||||
</data>
|
||||
<data name="QuestionSupportmailAnswerOperator" xml:space="preserve">
|
||||
<value>{0} request</value>
|
||||
</data>
|
||||
<data name="QuestionSupportmailAttachment" xml:space="preserve">
|
||||
<value>Attach file containing diagnosis information to mail?</value>
|
||||
</data>
|
||||
<data name="QuestionSupportmailSubject" xml:space="preserve">
|
||||
<value>Does your request/ comment relate to the {0}-app or to a more general subject?</value>
|
||||
</data>
|
||||
<data name="QuestionTitle" xml:space="preserve">
|
||||
<value>Question</value>
|
||||
</data>
|
||||
<data name="ActivityTextBikesAtStationGetBikes" xml:space="preserve">
|
||||
<value>Loading bikes located at station...</value>
|
||||
</data>
|
||||
<data name="ActivityTextCenterMap" xml:space="preserve">
|
||||
<value>Centering map...</value>
|
||||
</data>
|
||||
<data name="ActivityTextMyBikesCheckBluetoothState" xml:space="preserve">
|
||||
<value>Check Bluetooth state and location permissions...</value>
|
||||
</data>
|
||||
<data name="ActivityTextMyBikesLoadingBikes" xml:space="preserve">
|
||||
<value>Loading reserved/ booked bikes...</value>
|
||||
</data>
|
||||
<data name="ActivityTextSearchBikes" xml:space="preserve">
|
||||
<value>Searching locks...</value>
|
||||
</data>
|
||||
<data name="StatusTextBookedCodeSince" xml:space="preserve">
|
||||
<value>Code {0}, rented since {1}.</value>
|
||||
</data>
|
||||
<data name="StatusTextBooked" xml:space="preserve">
|
||||
<value>Bike is rented.</value>
|
||||
</data>
|
||||
<data name="StatusTextBookedSince" xml:space="preserve">
|
||||
<value>Rented since {0}.</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredMaximumReservationTime" xml:space="preserve">
|
||||
<value>Max. reservation time of {0} minutes expired.</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredCodeRemaining" xml:space="preserve">
|
||||
<value>Code {0}, still {1} minutes reserved.</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredRemaining" xml:space="preserve">
|
||||
<value>Still {0} minutes reserved.</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredCodeMaxReservationTime" xml:space="preserve">
|
||||
<value>Code {0}, max. reservation time of {1} minutes expired.</value>
|
||||
</data>
|
||||
<data name="StatusTextAvailable" xml:space="preserve">
|
||||
<value>Available.</value>
|
||||
</data>
|
||||
<data name="StatusTextBookedCodeLocationSince" xml:space="preserve">
|
||||
<value>Code {0}, location {1}, rented since {2}.</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredCodeLocationMaxReservationTime" xml:space="preserve">
|
||||
<value>Code {0}, location {1}, max. reservation time of {2} minutes expired.</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredCodeLocationReservationTime" xml:space="preserve">
|
||||
<value>Code {0}, location {1}, still {2} minutes reserved.</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredLocationMaxReservationTime" xml:space="preserve">
|
||||
<value>Location {0}, max. reservation time of {1} minutes expired.</value>
|
||||
</data>
|
||||
<data name="StatusTextReservationExpiredLocationReservationTime" xml:space="preserve">
|
||||
<value>Location {0}, still {1} minutes reserved.</value>
|
||||
</data>
|
||||
<data name="ActionContactRate" xml:space="preserve">
|
||||
<value>Submit rating</value>
|
||||
</data>
|
||||
<data name="ActionLoginLogin" xml:space="preserve">
|
||||
<value>Login</value>
|
||||
</data>
|
||||
<data name="ActionLoginPasswordForgotten" xml:space="preserve">
|
||||
<value>Password forgotten?</value>
|
||||
</data>
|
||||
<data name="ActionLoginRegister" xml:space="preserve">
|
||||
<value>Register</value>
|
||||
</data>
|
||||
<data name="MarkingLoginEmailAddressLabel" xml:space="preserve">
|
||||
<value>E-mail address</value>
|
||||
</data>
|
||||
<data name="MarkingLoginEmailAddressPlaceholder" xml:space="preserve">
|
||||
<value>E-mail address</value>
|
||||
</data>
|
||||
<data name="MarkingLoginInstructions" xml:space="preserve">
|
||||
<value>Instructions TINK bikes</value>
|
||||
</data>
|
||||
<data name="MarkingLoginInstructionsTinkKonradMessage" xml:space="preserve">
|
||||
<value>If you already have a "konrad" or "TINK" account, you can stop using both rental bike systems with your existing account! Simply confirm the corresponding terms and conditions.</value>
|
||||
</data>
|
||||
<data name="MarkingLoginInstructionsTinkKonradTitle" xml:space="preserve">
|
||||
<value>For your information!
|
||||
</value>
|
||||
</data>
|
||||
<data name="MarkingLoginPasswordLabel" xml:space="preserve">
|
||||
<value>Password, minimum length 8 characters</value>
|
||||
</data>
|
||||
<data name="MarkingLoginPasswordPlaceholder" xml:space="preserve">
|
||||
<value>Password</value>
|
||||
</data>
|
||||
<data name="MessageLoginConnectionErrorMessage" xml:space="preserve">
|
||||
<value>Login cookie must not be empty. {0}</value>
|
||||
</data>
|
||||
<data name="MessageLoginConnectionErrorTitle" xml:space="preserve">
|
||||
<value>Connection error during registration!</value>
|
||||
</data>
|
||||
<data name="MessageLoginErrorTitle" xml:space="preserve">
|
||||
<value>Error during login!</value>
|
||||
</data>
|
||||
<data name="MessageLoginRecoverPassword" xml:space="preserve">
|
||||
<value>Please connect to Internet to recover the password.</value>
|
||||
</data>
|
||||
<data name="MessageLoginRegisterNoNet" xml:space="preserve">
|
||||
<value>Please connect to Internet to register.</value>
|
||||
</data>
|
||||
<data name="MessageTitleHint" xml:space="preserve">
|
||||
<value>Hint</value>
|
||||
</data>
|
||||
<data name="ErrorOpenLockStillClosedMessage" xml:space="preserve">
|
||||
<value>After try to open lock state closed is reported.</value>
|
||||
</data>
|
||||
<data name="ErrorOpenLockOutOfReadMessage" xml:space="preserve">
|
||||
<value>Lock cannot be opened until bike is near.</value>
|
||||
</data>
|
||||
<data name="ErrorOpenLockTitle" xml:space="preserve">
|
||||
<value>Lock can not be opened!</value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockOutOfReachMessage" xml:space="preserve">
|
||||
<value>Lock cannot be closed until bike is near.</value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockOutOfReachStateReservedMessage" xml:space="preserve">
|
||||
<value>Lock cannot be closed until bike is near.
|
||||
Please try again to close bike or report bike to support!</value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockTitle" xml:space="preserve">
|
||||
<value>Lock can not be closed!</value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockStillOpenMessage" xml:space="preserve">
|
||||
<value>After try to close lock state open is reported.</value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockUnexpectedStateMessage" xml:space="preserve">
|
||||
<value>Lock reports state "{0}".</value>
|
||||
</data>
|
||||
<data name="ErrorCloseLockUnkErrorMessage" xml:space="preserve">
|
||||
<value>Please try to lock again or report bike to support!
|
||||
{0}</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementLocationPermission" xml:space="preserve">
|
||||
<value>Please allow location sharing so that bike lock/locks can be managed.</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementLocationPermissionOpenDialog" xml:space="preserve">
|
||||
<value>Please allow location sharing so that bike lock/locks can be managed.
|
||||
Open sharing dialog?</value>
|
||||
</data>
|
||||
<data name="MessageCenterMapLocationPermissionOpenDialog" xml:space="preserve">
|
||||
<value>Please allow location sharing so that map can be centered.
|
||||
Open sharing dialog?</value>
|
||||
</data>
|
||||
<data name="MessageAnswerNo" xml:space="preserve">
|
||||
<value>No</value>
|
||||
</data>
|
||||
<data name="MessageAnswerYes" xml:space="preserve">
|
||||
<value>Yes</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementBluetoothActivation" xml:space="preserve">
|
||||
<value>Please enable Bluetooth to manage bike lock/locks.</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementLocationActivation" xml:space="preserve">
|
||||
<value>Please activate location so that bike lock can be found!</value>
|
||||
</data>
|
||||
<data name="ActionSearchLock" xml:space="preserve">
|
||||
<value>Search lock</value>
|
||||
</data>
|
||||
<data name="ActivityTextOneMomentPlease" xml:space="preserve">
|
||||
<value>One moment please...</value>
|
||||
</data>
|
||||
<data name="ActivityTextOpeningLock" xml:space="preserve">
|
||||
<value>Opening lock...</value>
|
||||
</data>
|
||||
<data name="ActivityTextStartingUpdater" xml:space="preserve">
|
||||
<value>Updating...</value>
|
||||
</data>
|
||||
<data name="ActivityTextStartingUpdatingLockingState" xml:space="preserve">
|
||||
<value>Updating lock state...</value>
|
||||
</data>
|
||||
<data name="ActivityTextReadingChargingLevel" xml:space="preserve">
|
||||
<value>Reading charging level...</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorStatusUpdateingLockstate" xml:space="preserve">
|
||||
<value>Status error on updating lock state.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorConnectionUpdateingLockstate" xml:space="preserve">
|
||||
<value>Connection error on updating locking status.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorNoWebUpdateingLockstate" xml:space="preserve">
|
||||
<value>No web error on updating locking status.</value>
|
||||
</data>
|
||||
<data name="ActivityTextClosingLock" xml:space="preserve">
|
||||
<value>Closing lock...</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_203" xml:space="preserve">
|
||||
<value>Updated to latest lock firmware.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_204" xml:space="preserve">
|
||||
<value>Bluetooth communication inproved.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_205" xml:space="preserve">
|
||||
<value>Nicer station markers for iOS.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_206" xml:space="preserve">
|
||||
<value>Bluetooth and geolocation functionality improved.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_207" xml:space="preserve">
|
||||
<value>Minor fixes related to renting functionality.
|
||||
Software packages updated.
|
||||
Targets Android 11.</value>
|
||||
</data>
|
||||
<data name="ActivityTextReservingBike" xml:space="preserve">
|
||||
<value>Reserving bike...</value>
|
||||
</data>
|
||||
<data name="MessageOpenLockAndBookeBike" xml:space="preserve">
|
||||
<value>Rent bike {0} and open lock?</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorReadingChargingLevelGeneral" xml:space="preserve">
|
||||
<value>Battery status cannot be read.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorReadingChargingLevelOutOfReach" xml:space="preserve">
|
||||
<value>Battery status can only be read when bike is nearby.</value>
|
||||
</data>
|
||||
<data name="ActivityTextRentingBike" xml:space="preserve">
|
||||
<value>Renting bike...</value>
|
||||
</data>
|
||||
<data name="MessageRentingBikeErrorConnectionTitle" xml:space="preserve">
|
||||
<value>Connection error when renting the bike!</value>
|
||||
</data>
|
||||
<data name="MessageRentingBikeErrorGeneralTitle" xml:space="preserve">
|
||||
<value>Error when renting the bike!</value>
|
||||
</data>
|
||||
<data name="MessageRentingBikeErrorTooManyReservationsRentals" xml:space="preserve">
|
||||
<value>A rental of bike {0} was rejected because the maximum allowed number of {1} reservations/ rentals had already been made.</value>
|
||||
</data>
|
||||
<data name="MessageReservationBikeErrorTooManyReservationsRentals" xml:space="preserve">
|
||||
<value>A reservation of bike {0} was rejected because the maximum allowed number of {1} reservations/ rentals had already been made.</value>
|
||||
</data>
|
||||
<data name="MessageErrorLockIsClosedThreeLines" xml:space="preserve">
|
||||
<value>Attention: Lock is closed!
|
||||
{0}
|
||||
{1}</value>
|
||||
</data>
|
||||
<data name="MessageErrorLockIsClosedTwoLines" xml:space="preserve">
|
||||
<value>Attention: Lock is closed!
|
||||
{0}</value>
|
||||
</data>
|
||||
<data name="ExceptionTextRentingBikeFailedGeneral" xml:space="preserve">
|
||||
<value>The rental of bike No. {0} has failed.</value>
|
||||
</data>
|
||||
<data name="ExceptionTextRentingBikeFailedUnavailalbe" xml:space="preserve">
|
||||
<value>The rental of bike No. {0} has failed, the bike is currently unavailable.{1}</value>
|
||||
</data>
|
||||
<data name="ExceptionTextReservationBikeFailedGeneral" xml:space="preserve">
|
||||
<value>The reservation of bike no. {0} has failed.</value>
|
||||
</data>
|
||||
<data name="ExceptionTextReservationBikeFailedUnavailalbe" xml:space="preserve">
|
||||
<value>The reservation of bike No. {0} has failed, the bike is currently unavailable.{1}</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_208" xml:space="preserve">
|
||||
<value>Minor fixes.</value>
|
||||
</data>
|
||||
<data name="ActivityTextDisconnectingLock" xml:space="preserve">
|
||||
<value>Disconnecting lock...</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorDisconnect" xml:space="preserve">
|
||||
<value>Error occurred disconnecting</value>
|
||||
</data>
|
||||
<data name="MarkingBikeInfoErrorStateDisposableClosedDetected" xml:space="preserve">
|
||||
<value>Invalid state for bike {0} detected.
|
||||
Bike should always be disconnected when not reserved or rented.
|
||||
Please restart app in order to get bike info.</value>
|
||||
</data>
|
||||
<data name="MarkingBikeInfoErrorStateUnknownDetected" xml:space="preserve">
|
||||
<value>Unknown status for bike {0} detected.</value>
|
||||
</data>
|
||||
<data name="ActivityTextCancelingReservation" xml:space="preserve">
|
||||
<value>Canceling reservation...</value>
|
||||
</data>
|
||||
<data name="QuestionCancelReservation" xml:space="preserve">
|
||||
<value>Cancel reservation for bike {0}?</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_209" xml:space="preserve">
|
||||
<value>Minor fix: Bikes are disconnected as soon as becoming disposable.</value>
|
||||
</data>
|
||||
<data name="QuestionReserveBike" xml:space="preserve">
|
||||
<value>Reserve bike {0} free of charge for {1} min?</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorDeserializationException" xml:space="preserve">
|
||||
<value>Connection error: Deserialization failed.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorException" xml:space="preserve">
|
||||
<value>Connection interrupted.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorInvalidResponseException" xml:space="preserve">
|
||||
<value>Connection error, invalid server response.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorWebConnectFailureException" xml:space="preserve">
|
||||
<value>Connection interrupted, server unreachable.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorWebException" xml:space="preserve">
|
||||
<value>Connection error. Code: {0}.</value>
|
||||
</data>
|
||||
<data name="ActivityTextErrorWebForbiddenException" xml:space="preserve">
|
||||
<value>Connection interrupted, server busy.</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementTariffDescriptionAboEuroPerMonth" xml:space="preserve">
|
||||
<value>Subscription price</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementTariffDescriptionEuroPerHour" xml:space="preserve">
|
||||
<value>€/hour</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementTariffDescriptionFeeEuroPerHour" xml:space="preserve">
|
||||
<value>Rental fees</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementTariffDescriptionFreeTimePerSession" xml:space="preserve">
|
||||
<value>Free use</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementTariffDescriptionHour" xml:space="preserve">
|
||||
<value>hour(s)/day</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementTariffDescriptionMaxFeeEuroPerDay" xml:space="preserve">
|
||||
<value>Max. fee</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementTariffDescriptionTariffHeader" xml:space="preserve">
|
||||
<value>Tariff {0}, nr. {1}</value>
|
||||
</data>
|
||||
<data name="ActivityTextQuerryServer" xml:space="preserve">
|
||||
<value>Request server...</value>
|
||||
</data>
|
||||
<data name="ActivityTextSearchingLock" xml:space="preserve">
|
||||
<value>Searching lock...</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_214" xml:space="preserve">
|
||||
<value>Multiple operators support.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_215" xml:space="preserve">
|
||||
<value>Layout of "Whats New"-dialog improved :-)</value>
|
||||
</data>
|
||||
<data name="MessageBikesManagementMaxFeeEuroPerDay" xml:space="preserve">
|
||||
<value>€/day</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_216" xml:space="preserve">
|
||||
<value>GUI layout improved.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_217" xml:space="preserve">
|
||||
<value>Packages updated.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_218" xml:space="preserve">
|
||||
<value>Minor fixes.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_219" xml:space="preserve">
|
||||
<value>Icons added to flyout menu.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_220" xml:space="preserve">
|
||||
<value>GUI layout improved.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_221" xml:space="preserve">
|
||||
<value>GUI layout improved.</value>
|
||||
</data>
|
||||
<data name="ChangeLog3_0_222" xml:space="preserve">
|
||||
<value>Minor bugfix and improvements.</value>
|
||||
</data>
|
||||
</root>
|
730
TINKLib/MultilingualResources/TINKLib.de.xlf
Normal file
730
TINKLib/MultilingualResources/TINKLib.de.xlf
Normal file
|
@ -0,0 +1,730 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
|
||||
<file datatype="xml" source-language="en-GB" target-language="de" original="TINKLIB/MULTILINGUALRESOURCES/APPRESOURCES.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
|
||||
<header>
|
||||
<tool tool-id="MultilingualAppToolkit" tool-name="Multilingual App Toolkit" tool-version="4.0.6916.0" tool-company="Microsoft" />
|
||||
</header>
|
||||
<body>
|
||||
<group id="TINKLIB/MULTILINGUALRESOURCES/APPRESOURCES.RESX" datatype="resx">
|
||||
<trans-unit id="ActionBookOrClose" translate="yes" xml:space="preserve">
|
||||
<source>Rent bike or close lock</source>
|
||||
<target state="translated">Rad mieten oder Schloss schließen</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActionCancelRequest" translate="yes" xml:space="preserve">
|
||||
<source>Cancel bike reservation</source>
|
||||
<target state="final">Reservierung aufheben</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActionClose" translate="yes" xml:space="preserve">
|
||||
<source>Close lock</source>
|
||||
<target state="final">Schloss schließen</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActionCloseAndReturn" translate="yes" xml:space="preserve">
|
||||
<source>Close lock & return bike</source>
|
||||
<target state="final">Schloss schließen & Miete beenden</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActionOpen" translate="yes" xml:space="preserve">
|
||||
<source>Open lock</source>
|
||||
<target state="final">Schloss öffnen</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActionOpenAndBook" translate="yes" xml:space="preserve">
|
||||
<source>Open lock & rent bike</source>
|
||||
<target state="final">Schloss öffnen & Rad mieten</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActionOpenAndPause" translate="yes" xml:space="preserve">
|
||||
<source>Open lock & continue renting</source>
|
||||
<target state="final">Schloss öffnen & Miete fortsetzen</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActionRequest" translate="yes" xml:space="preserve">
|
||||
<source>Reserve bike</source>
|
||||
<target state="final">Rad reservieren</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActionReturn" translate="yes" xml:space="preserve">
|
||||
<source>Return bike</source>
|
||||
<target state="final">Miete beenden</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingMapPage" translate="yes" xml:space="preserve">
|
||||
<source>Bike Locations</source>
|
||||
<target state="translated">Fahrradstandorte</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageLoginWelcome" translate="yes" xml:space="preserve">
|
||||
<source>User {0} successfully logged in.</source>
|
||||
<target state="final">Benutzer {0} erfolgreich angemeldet.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageLoginWelcomeTitle" translate="yes" xml:space="preserve">
|
||||
<source>Welcome!</source>
|
||||
<target state="final">Willkommen!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageLoginWelcomeTitleGroup" translate="yes" xml:space="preserve">
|
||||
<source>Welcome to {0}!</source>
|
||||
<target state="translated">Wilkommen bei {0}!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingLoggedInStateInfoLoggedIn" translate="yes" xml:space="preserve">
|
||||
<source>Logged in as {0}.</source>
|
||||
<target state="final">Angemeldet als {0}.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingLoggedInStateInfoLoggedInGroup" translate="yes" xml:space="preserve">
|
||||
<source>Logged in as {0} at {1}.</source>
|
||||
<target state="final">Angemeldet als {0} bei {1}.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingLoggedInStateInfoNotLoggedIn" translate="yes" xml:space="preserve">
|
||||
<source>No user logged in.</source>
|
||||
<target state="final">Kein Benutzer angemeldet.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageAppVersionIsOutdated" translate="yes" xml:space="preserve">
|
||||
<source>This version of the {0} App is outdated. Please update to the latest version.</source>
|
||||
<target state="translated">Diese version der {0} App ist veraltet. Bitte auf aktuelle Version aktualisieren.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="QuestionSupportmailSubject" translate="yes" xml:space="preserve">
|
||||
<source>Does your request/ comment relate to the {0}-app or to a more general subject?</source>
|
||||
<target state="translated">Betrifft die Anfrage/ Anmerkung die {0}-App oder ein allgemeines Thema?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="QuestionSupportmailAnswerApp" translate="yes" xml:space="preserve">
|
||||
<source>{0} app request</source>
|
||||
<target state="final">{0}-App Anfrage</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="QuestionSupportmailAnswerOperator" translate="yes" xml:space="preserve">
|
||||
<source>{0} request</source>
|
||||
<target state="final">{0} Anfrage</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingAbout" translate="yes" xml:space="preserve">
|
||||
<source>About {0}</source>
|
||||
<target state="final">Über {0}</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingAccount" translate="yes" xml:space="preserve">
|
||||
<source>Account</source>
|
||||
<target state="final">Konto</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingFeedbackAndContact" translate="yes" xml:space="preserve">
|
||||
<source>Contact</source>
|
||||
<target state="translated">Kontakt</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingLogin" translate="yes" xml:space="preserve">
|
||||
<source>Login</source>
|
||||
<target state="final">Anmelden</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingMyBikes" translate="yes" xml:space="preserve">
|
||||
<source>My Bikes</source>
|
||||
<target state="final">Meine Räder</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingFeesAndBikes" translate="yes" xml:space="preserve">
|
||||
<source>Instructions</source>
|
||||
<target state="translated">Bedienung</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingSettings" translate="yes" xml:space="preserve">
|
||||
<source>Settings</source>
|
||||
<target state="final">Einstellungen</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingTabBikes" translate="yes" xml:space="preserve">
|
||||
<source>Instructions</source>
|
||||
<target state="final">Bedienung</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingTabFees" translate="yes" xml:space="preserve">
|
||||
<source>Pricing</source>
|
||||
<target state="translated">Tarife</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorOpenLockMessage" translate="yes" xml:space="preserve">
|
||||
<source>Lock is blocked. Please ensure that no obstacle prevents lock from opening and try again.</source>
|
||||
<target state="translated">Schloss ist blockiert. Bitte Ursache von Blockierung beheben und Vorgang wiederholen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorCloseLockBoldBlockedMessage" translate="yes" xml:space="preserve">
|
||||
<source>Lock is blocked. Please ensure that no spoke or any other obstacle prevents the lock from closing and try again.</source>
|
||||
<target state="translated">Schloss ist blockiert. Bitte sicherstellen, dass keine Speiche oder ein anderer Gegenstand das Schloss blockiert und Vorgang wiederholen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorCloseLockMovingMessage" translate="yes" xml:space="preserve">
|
||||
<source>Lock can only be closed if bike is not moving. Please park bike and try again.</source>
|
||||
<target state="final">Schloss kann erst geschlossen werden, wenn Rad nicht mehr bewegt wird. Bitte Rad abstellen und Vorgang wiederholen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorBookedSearchMessage" translate="yes" xml:space="preserve">
|
||||
<source>Lock of rented bike can not be found.</source>
|
||||
<target state="final">Schloss des gemieteten Rads kann nicht gefunden werden.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorReservedSearchMessage" translate="yes" xml:space="preserve">
|
||||
<source>Lock of reserved bike can not be found.</source>
|
||||
<target state="translated">Schloss des reservierten Rads kann nicht gefunden werden.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextBikesAtStationGetBikes" translate="yes" xml:space="preserve">
|
||||
<source>Loading bikes located at station...</source>
|
||||
<target state="translated">Lade Räder an Station...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextMyBikesLoadingBikes" translate="yes" xml:space="preserve">
|
||||
<source>Loading reserved/ booked bikes...</source>
|
||||
<target state="translated">Lade meine Räder...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextSearchBikes" translate="yes" xml:space="preserve">
|
||||
<source>Searching locks...</source>
|
||||
<target state="translated">Suche Schlösser...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextMyBikesCheckBluetoothState" translate="yes" xml:space="preserve">
|
||||
<source>Check Bluetooth state and location permissions...</source>
|
||||
<target state="translated">Prüfe Berechtigungen...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextCenterMap" translate="yes" xml:space="preserve">
|
||||
<source>Centering map...</source>
|
||||
<target state="translated">Zentriere Karte...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextMapLoadingStationsAndBikes" translate="yes" xml:space="preserve">
|
||||
<source>Loading Stations and Bikes...</source>
|
||||
<target state="translated">Lade Stationen und Räder...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorReturnBikeNotAtStationMessage" translate="yes" xml:space="preserve">
|
||||
<source>Returning bike outside of station is not possible. Distance to station {0} is {1} m.</source>
|
||||
<target state="translated">Rückgabe ausserhalb von Station nicht möglich. Entfernung zur Station {0} ist {1} m.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorReturnBikeNotAtStationTitle" translate="yes" xml:space="preserve">
|
||||
<source>Error returning bike!</source>
|
||||
<target state="translated">Fehler bei Radrückgabe!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorReturnBikeLockClosedNoGPSMessage" translate="yes" xml:space="preserve">
|
||||
<source>Returning bike at an unknown location is not possible.
|
||||
Bike can be returned if
|
||||
- location information is available when closing lock
|
||||
- bike is in reach and location information is available when pressing button "Return bike"</source>
|
||||
<target state="translated">Fahrradrückgabe an unbekanntem Standort nicht möglich.
|
||||
Eine Radrückgabe ist möglich, wenn
|
||||
- beim Schliessen des Schlosses Standortinformation verfügbar ist
|
||||
- beim Drücken von "Rad zurückgeben" das Rad in Reichweite ist und Standortinformation verfügbar ist.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorReturnBikeLockOpenNoGPSMessage" translate="yes" xml:space="preserve">
|
||||
<source>Returning bike at an unknown location is not possible.
|
||||
Bike can only be returned if bike is in reach and location information is available.</source>
|
||||
<target state="translated">Fahrradrückgabe an unbekanntem Standort nicht möglich.
|
||||
Eine Radrückgabe ist nur möglich, wenn das Rad in Reichweite ist und Standortinformation verfügbar ist.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorSupportmailCreateAttachment" translate="yes" xml:space="preserve">
|
||||
<source>Attachment could not be created.</source>
|
||||
<target state="translated">Mailanhang konnte nich erzeugt werden.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorSupportmailMailingFailed" translate="yes" xml:space="preserve">
|
||||
<source>Opening mail app failed.</source>
|
||||
<target state="translated">Mailapp konnte nicht geöffnet werden.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorSupportmailPhoningFailed" translate="yes" xml:space="preserve">
|
||||
<source>Opening phone app failed.</source>
|
||||
<target state="translated">Telefonapp konnte nicht geöffnet werden.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageAnswerOk" translate="yes" xml:space="preserve">
|
||||
<source>OK</source>
|
||||
<target state="translated">OK</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageContactMail" translate="yes" xml:space="preserve">
|
||||
<source>Questions? Remarks? Criticism?</source>
|
||||
<target state="translated">Fragen? Hinweise? Kritik?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessagePhoneMail" translate="yes" xml:space="preserve">
|
||||
<source>Urgent question related to {0}? (Monday-Friday: 10:00 18:00)</source>
|
||||
<target state="translated">Eilige Frage rund um {0}? (Montag-Freitag: 10:00 18:00)</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageRateMail" translate="yes" xml:space="preserve">
|
||||
<source>Are you enjoying the {0}-App?</source>
|
||||
<target state="translated">Gefällt die {0}-App?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageWaring" translate="yes" xml:space="preserve">
|
||||
<source>Warning</source>
|
||||
<target state="translated">Warnung</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="QuestionAnswerNo" translate="yes" xml:space="preserve">
|
||||
<source>No</source>
|
||||
<target state="translated">Nein</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="QuestionAnswerYes" translate="yes" xml:space="preserve">
|
||||
<source>Yes</source>
|
||||
<target state="translated">Ja</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="QuestionSupportmailAttachment" translate="yes" xml:space="preserve">
|
||||
<source>Attach file containing diagnosis information to mail?</source>
|
||||
<target state="translated">Soll der Mail eine Datei mit Diagnoseinformationen angehängt werden?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="QuestionTitle" translate="yes" xml:space="preserve">
|
||||
<source>Question</source>
|
||||
<target state="translated">Frage</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageMapPageErrorAuthcookieUndefined" translate="yes" xml:space="preserve">
|
||||
<source>Session has expired.
|
||||
Either there are more than 8 devices in use or the user's account is no longer valid.
|
||||
Use of app is restricted to maximu 8 devices per account.
|
||||
Please login to app once again. In case this fails please check on website if the account is still valid.</source>
|
||||
<target state="translated">Sitzung ist abgelaufen.
|
||||
Entweder es sind mehr als 8 Geräte in Benutzung oder das Konto ist nicht mehr gültig.
|
||||
Die Nutzung der App ist auf maximal 8 Geräten pro Konto möglich.
|
||||
Bitte erneut in App anmelden. Sollte dies fehlschlagen bitte auf Website prüfen, ob das Konto noch gültig ist.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingBikesAtStationTitle" translate="yes" xml:space="preserve">
|
||||
<source>Bike Location {0}</source>
|
||||
<target state="translated">Fahrradstandort {0}</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="StatusTextReservationExpiredCodeMaxReservationTime" translate="yes" xml:space="preserve">
|
||||
<source>Code {0}, max. reservation time of {1} minutes expired.</source>
|
||||
<target state="translated">Code ist {0}, max. Reservierungszeit von {1} Min. abgelaufen.
|
||||
</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="StatusTextReservationExpiredCodeRemaining" translate="yes" xml:space="preserve">
|
||||
<source>Code {0}, still {1} minutes reserved.</source>
|
||||
<target state="translated">Code ist {0}, noch {1} Minuten reserviert.
|
||||
</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="StatusTextBooked" translate="yes" xml:space="preserve">
|
||||
<source>Bike is rented.</source>
|
||||
<target state="translated">Rad ist gemietet.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="StatusTextBookedSince" translate="yes" xml:space="preserve">
|
||||
<source>Rented since {0}.</source>
|
||||
<target state="translated">Gemietet seit {0}.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="StatusTextReservationExpiredRemaining" translate="yes" xml:space="preserve">
|
||||
<source>Still {0} minutes reserved.</source>
|
||||
<target state="translated">Noch {0} Minuten reserviert.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="StatusTextReservationExpiredMaximumReservationTime" translate="yes" xml:space="preserve">
|
||||
<source>Max. reservation time of {0} minutes expired.</source>
|
||||
<target state="translated">Max. Reservierungszeit von {0} Min. abgelaufen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="StatusTextBookedCodeSince" translate="yes" xml:space="preserve">
|
||||
<source>Code {0}, rented since {1}.</source>
|
||||
<target state="translated">Code ist {0}, gemietet seit {1}.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="StatusTextReservationExpiredLocationMaxReservationTime" translate="yes" xml:space="preserve">
|
||||
<source>Location {0}, max. reservation time of {1} minutes expired.</source>
|
||||
<target state="translated">Standort {0}, max. Reservierungszeit von {1} Min. abgelaufen.
|
||||
</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="StatusTextAvailable" translate="yes" xml:space="preserve">
|
||||
<source>Available.</source>
|
||||
<target state="translated">Frei.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="StatusTextBookedCodeLocationSince" translate="yes" xml:space="preserve">
|
||||
<source>Code {0}, location {1}, rented since {2}.</source>
|
||||
<target state="translated">Code {0}, Standort {1}, gemietet seit {2}.
|
||||
</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="StatusTextReservationExpiredCodeLocationMaxReservationTime" translate="yes" xml:space="preserve">
|
||||
<source>Code {0}, location {1}, max. reservation time of {2} minutes expired.</source>
|
||||
<target state="translated">Code ist {0}, Standort {1}, max. Reservierungszeit von {2} Min. abgelaufen.
|
||||
</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="StatusTextReservationExpiredCodeLocationReservationTime" translate="yes" xml:space="preserve">
|
||||
<source>Code {0}, location {1}, still {2} minutes reserved.</source>
|
||||
<target state="translated">Code ist {0}, Standort {1}, noch {2} Minuten reserviert.
|
||||
</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="StatusTextReservationExpiredLocationReservationTime" translate="yes" xml:space="preserve">
|
||||
<source>Location {0}, still {1} minutes reserved.</source>
|
||||
<target state="translated">Standort {0}, noch {1} Minuten reserviert.
|
||||
</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActionLoginLogin" translate="yes" xml:space="preserve">
|
||||
<source>Login</source>
|
||||
<target state="translated">Anmelden</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActionLoginPasswordForgotten" translate="yes" xml:space="preserve">
|
||||
<source>Password forgotten?</source>
|
||||
<target state="translated">Passwort vergessen?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActionLoginRegister" translate="yes" xml:space="preserve">
|
||||
<source>Register</source>
|
||||
<target state="translated">Registrieren</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingLoginEmailAddressLabel" translate="yes" xml:space="preserve">
|
||||
<source>E-mail address</source>
|
||||
<target state="translated">E-Mail Adresse</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingLoginEmailAddressPlaceholder" translate="yes" xml:space="preserve">
|
||||
<source>E-mail address</source>
|
||||
<target state="translated">E-Mail Adresse</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingLoginPasswordLabel" translate="yes" xml:space="preserve">
|
||||
<source>Password, minimum length 8 characters</source>
|
||||
<target state="translated">Passwort, Mindestlänge 8 Zeichen</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingLoginPasswordPlaceholder" translate="yes" xml:space="preserve">
|
||||
<source>Password</source>
|
||||
<target state="translated">Passwort</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActionContactRate" translate="yes" xml:space="preserve">
|
||||
<source>Submit rating</source>
|
||||
<target state="translated">Bewertung abgeben</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingLoginInstructions" translate="yes" xml:space="preserve">
|
||||
<source>Instructions TINK bikes</source>
|
||||
<target state="translated">Anleitung TINK Räder</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingLoginInstructionsTinkKonradMessage" translate="yes" xml:space="preserve">
|
||||
<source>If you already have a "konrad" or "TINK" account, you can stop using both rental bike systems with your existing account! Simply confirm the corresponding terms and conditions.</source>
|
||||
<target state="translated">Falls Sie bereits einen "konrad" oder "TINK" Account besitzen, können Sie mit Ihren bestehendem Account die Nutzung beider Mietradsysteme einstellen! Einfach dazu die entsprechende AGB bestätigen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingLoginInstructionsTinkKonradTitle" translate="yes" xml:space="preserve">
|
||||
<source>For your information!
|
||||
</source>
|
||||
<target state="translated">Zur Information!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageLoginConnectionErrorMessage" translate="yes" xml:space="preserve">
|
||||
<source>Login cookie must not be empty. {0}</source>
|
||||
<target state="translated">Anmeldungskeks darf nicht leer sein. {0}</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageLoginConnectionErrorTitle" translate="yes" xml:space="preserve">
|
||||
<source>Connection error during registration!</source>
|
||||
<target state="translated">Verbindungsfehler beim Registrieren!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageLoginErrorTitle" translate="yes" xml:space="preserve">
|
||||
<source>Error during login!</source>
|
||||
<target state="translated">Fehler bei der Anmeldung!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageLoginRecoverPassword" translate="yes" xml:space="preserve">
|
||||
<source>Please connect to Internet to recover the password.</source>
|
||||
<target state="translated">Bitte mit dem Internet verbinden zum Wiederherstellen des Passworts.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageLoginRegisterNoNet" translate="yes" xml:space="preserve">
|
||||
<source>Please connect to Internet to register.</source>
|
||||
<target state="translated">Bitte mit dem Internet verbinden zum Registrieren.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageTitleHint" translate="yes" xml:space="preserve">
|
||||
<source>Hint</source>
|
||||
<target state="translated">Hinweis</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorOpenLockStillClosedMessage" translate="yes" xml:space="preserve">
|
||||
<source>After try to open lock state closed is reported.</source>
|
||||
<target state="translated">Nach Versuch Schloss zu öffnen wird Status geschlossen zurückgemeldet.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorOpenLockOutOfReadMessage" translate="yes" xml:space="preserve">
|
||||
<source>Lock cannot be opened until bike is near.</source>
|
||||
<target state="translated">Schloss kann erst geöffnet werden, wenn Rad in der Nähe ist.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorOpenLockTitle" translate="yes" xml:space="preserve">
|
||||
<source>Lock can not be opened!</source>
|
||||
<target state="translated">Schloss kann nicht geöffnet werden!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorCloseLockOutOfReachMessage" translate="yes" xml:space="preserve">
|
||||
<source>Lock cannot be closed until bike is near.</source>
|
||||
<target state="translated">Schloss kann erst geschlossen werden, wenn das Rad in der Nähe ist. </target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorCloseLockOutOfReachStateReservedMessage" translate="yes" xml:space="preserve">
|
||||
<source>Lock cannot be closed until bike is near.
|
||||
Please try again to close bike or report bike to support!</source>
|
||||
<target state="translated">Schloss kann erst geschlossen werden, wenn das Rad in der Nähe ist.
|
||||
Bitte Schließen nochmals versuchen oder Rad dem Support melden!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorCloseLockTitle" translate="yes" xml:space="preserve">
|
||||
<source>Lock can not be closed!</source>
|
||||
<target state="translated">Schloss kann nicht geschlossen werden!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorCloseLockStillOpenMessage" translate="yes" xml:space="preserve">
|
||||
<source>After try to close lock state open is reported.</source>
|
||||
<target state="translated">Nach Versuch Schloss zu schließen wird Status geöffnet zurückgemeldet.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorCloseLockUnexpectedStateMessage" translate="yes" xml:space="preserve">
|
||||
<source>Lock reports state "{0}".</source>
|
||||
<target state="translated">Schloss meldet Status "{0}".</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ErrorCloseLockUnkErrorMessage" translate="yes" xml:space="preserve">
|
||||
<source>Please try to lock again or report bike to support!
|
||||
{0}</source>
|
||||
<target state="translated">Bitte schließen nochmals versuchen oder Rad dem Support melden!
|
||||
{0}</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageBikesManagementLocationPermission" translate="yes" xml:space="preserve">
|
||||
<source>Please allow location sharing so that bike lock/locks can be managed.</source>
|
||||
<target state="translated">Bitte Standortfreigabe erlauben, damit Fahrradschloss/ Schlösser verwaltet werden können.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageBikesManagementLocationPermissionOpenDialog" translate="yes" xml:space="preserve">
|
||||
<source>Please allow location sharing so that bike lock/locks can be managed.
|
||||
Open sharing dialog?</source>
|
||||
<target state="translated">Bitte Standortfreigabe erlauben, damit Fahrradschloss/ Schlösser verwaltet werden können.
|
||||
Freigabedialog öffen?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageCenterMapLocationPermissionOpenDialog" translate="yes" xml:space="preserve">
|
||||
<source>Please allow location sharing so that map can be centered.
|
||||
Open sharing dialog?</source>
|
||||
<target state="translated">Bitte Standortfreigabe erlauben, damit Karte zentriert werden werden kann.
|
||||
Freigabedialog öffen?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageBikesManagementLocationActivation" translate="yes" xml:space="preserve">
|
||||
<source>Please activate location so that bike lock can be found!</source>
|
||||
<target state="translated">Bitte Standort aktivieren, damit Fahrradschloss gefunden werden kann!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageAnswerNo" translate="yes" xml:space="preserve">
|
||||
<source>No</source>
|
||||
<target state="translated">Nein</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageAnswerYes" translate="yes" xml:space="preserve">
|
||||
<source>Yes</source>
|
||||
<target state="translated">Ja</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageBikesManagementBluetoothActivation" translate="yes" xml:space="preserve">
|
||||
<source>Please enable Bluetooth to manage bike lock/locks.</source>
|
||||
<target state="translated">Bitte Bluetooth aktivieren, damit Fahrradschloss/ Schlösser verwaltet werden können.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActionSearchLock" translate="yes" xml:space="preserve">
|
||||
<source>Search lock</source>
|
||||
<target state="translated">Schloss suchen</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextOneMomentPlease" translate="yes" xml:space="preserve">
|
||||
<source>One moment please...</source>
|
||||
<target state="translated">Einen Moment bitte...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextOpeningLock" translate="yes" xml:space="preserve">
|
||||
<source>Opening lock...</source>
|
||||
<target state="translated">Öffne Schloss...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextStartingUpdater" translate="yes" xml:space="preserve">
|
||||
<source>Updating...</source>
|
||||
<target state="translated">Starte Aktualisierung...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextStartingUpdatingLockingState" translate="yes" xml:space="preserve">
|
||||
<source>Updating lock state...</source>
|
||||
<target state="translated">Aktualisiere Schlossstatus...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextReadingChargingLevel" translate="yes" xml:space="preserve">
|
||||
<source>Reading charging level...</source>
|
||||
<target state="translated">Lese Akkustatus...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextErrorStatusUpdateingLockstate" translate="yes" xml:space="preserve">
|
||||
<source>Status error on updating lock state.</source>
|
||||
<target state="translated">Statusfehler beim Aktualisieren des Schlossstatusses.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextErrorConnectionUpdateingLockstate" translate="yes" xml:space="preserve">
|
||||
<source>Connection error on updating locking status.</source>
|
||||
<target state="translated">Verbingungsfehler beim Aktualisieren des Schlossstatusses.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextErrorNoWebUpdateingLockstate" translate="yes" xml:space="preserve">
|
||||
<source>No web error on updating locking status.</source>
|
||||
<target state="translated">Kein Netz beim Aktualisieren des Schlossstatusses.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextClosingLock" translate="yes" xml:space="preserve">
|
||||
<source>Closing lock...</source>
|
||||
<target state="translated">Schließe Schloss...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_203" translate="yes" xml:space="preserve">
|
||||
<source>Updated to latest lock firmware.</source>
|
||||
<target state="translated">Aktualisierrt auf aktuelle Schloss-Firmware.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_204" translate="yes" xml:space="preserve">
|
||||
<source>Bluetooth communication inproved.</source>
|
||||
<target state="translated">Bluetooth Kommunikation verbessert.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_205" translate="yes" xml:space="preserve">
|
||||
<source>Nicer station markers for iOS.</source>
|
||||
<target state="translated">Stationssymbole verbessert.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_206" translate="yes" xml:space="preserve">
|
||||
<source>Bluetooth and geolocation functionality improved.</source>
|
||||
<target state="translated">Bluetooth- und Geolocation-Funktionalität verbessert.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_207" translate="yes" xml:space="preserve">
|
||||
<source>Minor fixes related to renting functionality.
|
||||
Software packages updated.
|
||||
Targets Android 11.</source>
|
||||
<target state="translated">Kleinere Fehlerbehebungen.
|
||||
Software Pakete aktualisiert.
|
||||
Zielplatform Android 11.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageOpenLockAndBookeBike" translate="yes" xml:space="preserve">
|
||||
<source>Rent bike {0} and open lock?</source>
|
||||
<target state="translated">Fahrrad {0} mieten und Schloss öffnen?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextReservingBike" translate="yes" xml:space="preserve">
|
||||
<source>Reserving bike...</source>
|
||||
<target state="translated">Reserviere Rad...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextErrorReadingChargingLevelGeneral" translate="yes" xml:space="preserve">
|
||||
<source>Battery status cannot be read.</source>
|
||||
<target state="translated">Akkustatus kann nicht gelesen werden.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextErrorReadingChargingLevelOutOfReach" translate="yes" xml:space="preserve">
|
||||
<source>Battery status can only be read when bike is nearby.</source>
|
||||
<target state="translated">Akkustatus kann erst gelesen werden, wenn Rad in der Nähe ist.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextRentingBike" translate="yes" xml:space="preserve">
|
||||
<source>Renting bike...</source>
|
||||
<target state="translated">Miete Rad...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageRentingBikeErrorConnectionTitle" translate="yes" xml:space="preserve">
|
||||
<source>Connection error when renting the bike!</source>
|
||||
<target state="translated">Verbingungsfehler beim Mieten des Rads!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageRentingBikeErrorGeneralTitle" translate="yes" xml:space="preserve">
|
||||
<source>Error when renting the bike!</source>
|
||||
<target state="translated">Fehler beim Mieten des Rads!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageRentingBikeErrorTooManyReservationsRentals" translate="yes" xml:space="preserve">
|
||||
<source>A rental of bike {0} was rejected because the maximum allowed number of {1} reservations/ rentals had already been made.</source>
|
||||
<target state="translated">Eine Miete des Rads {0} wurde abgelehnt, weil die maximal erlaubte Anzahl von {1} Reservierungen/ Buchungen bereits getätigt wurden.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageReservationBikeErrorTooManyReservationsRentals" translate="yes" xml:space="preserve">
|
||||
<source>A reservation of bike {0} was rejected because the maximum allowed number of {1} reservations/ rentals had already been made.</source>
|
||||
<target state="translated">Eine Reservierung des Rads {0} wurde abgelehnt, weil die maximal erlaubte Anzahl von {1} Reservierungen/ Buchungen bereits getätigt wurden.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageErrorLockIsClosedThreeLines" translate="yes" xml:space="preserve">
|
||||
<source>Attention: Lock is closed!
|
||||
{0}
|
||||
{1}</source>
|
||||
<target state="translated">Achtung: Schloss wird geschlossen!
|
||||
{0}
|
||||
{1}</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageErrorLockIsClosedTwoLines" translate="yes" xml:space="preserve">
|
||||
<source>Attention: Lock is closed!
|
||||
{0}</source>
|
||||
<target state="translated">Achtung: Schloss wird geschlossen!
|
||||
{0}</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ExceptionTextRentingBikeFailedGeneral" translate="yes" xml:space="preserve">
|
||||
<source>The rental of bike No. {0} has failed.</source>
|
||||
<target state="translated">Die Miete des Fahrads Nr. {0} ist fehlgeschlagen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ExceptionTextRentingBikeFailedUnavailalbe" translate="yes" xml:space="preserve">
|
||||
<source>The rental of bike No. {0} has failed, the bike is currently unavailable.{1}</source>
|
||||
<target state="translated">Die Miete des Rads Nr. {0} ist fehlgeschlagen, das Rad ist momentan nicht erreichbar.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ExceptionTextReservationBikeFailedGeneral" translate="yes" xml:space="preserve">
|
||||
<source>The reservation of bike no. {0} has failed.</source>
|
||||
<target state="translated">Die Reservierung des Fahrads Nr. {0} ist fehlgeschlagen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ExceptionTextReservationBikeFailedUnavailalbe" translate="yes" xml:space="preserve">
|
||||
<source>The reservation of bike No. {0} has failed, the bike is currently unavailable.{1}</source>
|
||||
<target state="translated">Die Reservierung des Rads Nr. {0} ist fehlgeschlagen, das Rad ist momentan nicht erreichbar.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_208" translate="yes" xml:space="preserve">
|
||||
<source>Minor fixes.</source>
|
||||
<target state="translated">Kleinere Fehlerbehebungen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextDisconnectingLock" translate="yes" xml:space="preserve">
|
||||
<source>Disconnecting lock...</source>
|
||||
<target state="translated">Trenne Schloss...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextErrorDisconnect" translate="yes" xml:space="preserve">
|
||||
<source>Error occurred disconnecting</source>
|
||||
<target state="translated">Fehler beim Trennen...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingBikeInfoErrorStateDisposableClosedDetected" translate="yes" xml:space="preserve">
|
||||
<source>Invalid state for bike {0} detected.
|
||||
Bike should always be disconnected when not reserved or rented.
|
||||
Please restart app in order to get bike info.</source>
|
||||
<target state="translated">Ungülitiger Status von Rad {0} erkannt.
|
||||
Ein nicht reserviertes oder gemietetes Rad sollte immer getrennt sein .
|
||||
Bitte App neu starten um Rad Infos zu bekommen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MarkingBikeInfoErrorStateUnknownDetected" translate="yes" xml:space="preserve">
|
||||
<source>Unknown status for bike {0} detected.</source>
|
||||
<target state="translated">Ungültiger Mietstatus von Rad {0} erkannt.
|
||||
|
||||
</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextCancelingReservation" translate="yes" xml:space="preserve">
|
||||
<source>Canceling reservation...</source>
|
||||
<target state="translated">Reservierung aufheben...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="QuestionCancelReservation" translate="yes" xml:space="preserve">
|
||||
<source>Cancel reservation for bike {0}?</source>
|
||||
<target state="translated">Reservierung für Fahrrad {0} aufheben?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_209" translate="yes" xml:space="preserve">
|
||||
<source>Minor fix: Bikes are disconnected as soon as becoming disposable.</source>
|
||||
<target state="translated">Kleinere Fehlerbehebung: Verbindung wird getrennt, sobald Rad verfügbar ist.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="QuestionReserveBike" translate="yes" xml:space="preserve">
|
||||
<source>Reserve bike {0} free of charge for {1} min?</source>
|
||||
<target state="translated">Fahrrad {0} kostenlos für {1} Min. reservieren?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextErrorDeserializationException" translate="yes" xml:space="preserve">
|
||||
<source>Connection error: Deserialization failed.</source>
|
||||
<target state="translated">Verbindungsfehler: Deserialisierung fehlgeschlagen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextErrorException" translate="yes" xml:space="preserve">
|
||||
<source>Connection interrupted.</source>
|
||||
<target state="translated">Verbindung unterbrochen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextErrorInvalidResponseException" translate="yes" xml:space="preserve">
|
||||
<source>Connection error, invalid server response.</source>
|
||||
<target state="translated">Verbindungsfehler, ungülige Serverantwort.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextErrorWebConnectFailureException" translate="yes" xml:space="preserve">
|
||||
<source>Connection interrupted, server unreachable.</source>
|
||||
<target state="translated">Verbindung unterbrochen, Server nicht erreichbar.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextErrorWebException" translate="yes" xml:space="preserve">
|
||||
<source>Connection error. Code: {0}.</source>
|
||||
<target state="translated">Verbindungsfehler. Code: {0}.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextErrorWebForbiddenException" translate="yes" xml:space="preserve">
|
||||
<source>Connection interrupted, server busy.</source>
|
||||
<target state="translated">Verbindung unterbrochen, Server beschäftigt.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageBikesManagementTariffDescriptionAboEuroPerMonth" translate="yes" xml:space="preserve">
|
||||
<source>Subscription price</source>
|
||||
<target state="translated">Abo-Preis</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageBikesManagementTariffDescriptionFeeEuroPerHour" translate="yes" xml:space="preserve">
|
||||
<source>Rental fees</source>
|
||||
<target state="translated">Mietgebühren</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageBikesManagementTariffDescriptionFreeTimePerSession" translate="yes" xml:space="preserve">
|
||||
<source>Free use</source>
|
||||
<target state="translated">Gratis Nutzung</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageBikesManagementTariffDescriptionMaxFeeEuroPerDay" translate="yes" xml:space="preserve">
|
||||
<source>Max. fee</source>
|
||||
<target state="translated">Max. Gebühr</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageBikesManagementTariffDescriptionEuroPerHour" translate="yes" xml:space="preserve">
|
||||
<source>€/hour</source>
|
||||
<target state="translated">€/Std.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageBikesManagementTariffDescriptionHour" translate="yes" xml:space="preserve">
|
||||
<source>hour(s)/day</source>
|
||||
<target state="translated">Std./Tag</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageBikesManagementTariffDescriptionTariffHeader" translate="yes" xml:space="preserve">
|
||||
<source>Tariff {0}, nr. {1}</source>
|
||||
<target state="translated">Tarif {0}, Nr. {1}</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextQuerryServer" translate="yes" xml:space="preserve">
|
||||
<source>Request server...</source>
|
||||
<target state="translated">Anfrage Server...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ActivityTextSearchingLock" translate="yes" xml:space="preserve">
|
||||
<source>Searching lock...</source>
|
||||
<target state="translated">Suche Schloss...</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_214" translate="yes" xml:space="preserve">
|
||||
<source>Multiple operators support.</source>
|
||||
<target state="translated">Mehrbetreiber Unterstützung.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_215" translate="yes" xml:space="preserve">
|
||||
<source>Layout of "Whats New"-dialog improved :-)</source>
|
||||
<target state="translated">Layout des "Whats New"-Dialos verbessert :-)</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="MessageBikesManagementMaxFeeEuroPerDay" translate="yes" xml:space="preserve">
|
||||
<source>€/day</source>
|
||||
<target state="translated">€/Tag</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_216" translate="yes" xml:space="preserve">
|
||||
<source>GUI layout improved.</source>
|
||||
<target state="translated">Oberflächenlayout verbessert.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_217" translate="yes" xml:space="preserve">
|
||||
<source>Packages updated.</source>
|
||||
<target state="translated">Pakete aktualisiert.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_218" translate="yes" xml:space="preserve">
|
||||
<source>Minor fixes.</source>
|
||||
<target state="translated">Kleine Verbesserungen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_219" translate="yes" xml:space="preserve">
|
||||
<source>Icons added to flyout menu.</source>
|
||||
<target state="translated">Icons zum Flyout-Menü hinzugefügt.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_220" translate="yes" xml:space="preserve">
|
||||
<source>GUI layout improved.</source>
|
||||
<target state="translated">Oberflächenlayout verbessert.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_221" translate="yes" xml:space="preserve">
|
||||
<source>GUI layout improved.</source>
|
||||
<target state="translated">Oberflächenlayout verbessert.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ChangeLog3_0_222" translate="yes" xml:space="preserve">
|
||||
<source>Minor bugfix and improvements.</source>
|
||||
<target state="translated">Kleine Fehlerbehebungen und Verbesserungen.</target>
|
||||
</trans-unit>
|
||||
</group>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
785
TINKLib/Repository/CopriCallsHttps.cs
Normal file
785
TINKLib/Repository/CopriCallsHttps.cs
Normal file
|
@ -0,0 +1,785 @@
|
|||
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Repository.Exception;
|
||||
using TINK.Model.Repository.Request;
|
||||
using TINK.Model.Repository.Response;
|
||||
using TINK.Model.Logging;
|
||||
using TINK.Repository.Response;
|
||||
|
||||
namespace TINK.Model.Repository
|
||||
{
|
||||
/// <summary> Object which manages calls to copri. </summary>
|
||||
public class CopriCallsHttps : ICopriServer
|
||||
{
|
||||
/// <summary> Builds requests.</summary>
|
||||
private IRequestBuilder requestBuilder;
|
||||
|
||||
/// <summary> Initializes a instance of the copri calls https object. </summary>
|
||||
/// <param name="p_oCopriHost">Host to connect to. </param>
|
||||
/// <param name="p_strMerchantId">Id of the merchant.</param>
|
||||
/// <param name="userAgent">Holds the name and version of the TINKApp.</param>
|
||||
/// <param name="sessionCookie">Session cookie if user is logged in, null otherwise.</param>
|
||||
public CopriCallsHttps(
|
||||
Uri p_oCopriHost,
|
||||
string p_strMerchantId,
|
||||
string userAgent,
|
||||
string sessionCookie = null)
|
||||
{
|
||||
m_oCopriHost = p_oCopriHost
|
||||
?? throw new System.Exception($"Can not construct {GetType().ToString()}- object. Uri of copri host must not be null.");
|
||||
|
||||
UserAgent = !string.IsNullOrEmpty(userAgent)
|
||||
? userAgent
|
||||
: throw new System.Exception($"Can not construct {GetType().ToString()}- object. User agent must not be null or empty.");
|
||||
|
||||
requestBuilder = string.IsNullOrEmpty(sessionCookie)
|
||||
? new RequestBuilder(p_strMerchantId) as IRequestBuilder
|
||||
: new RequestBuilderLoggedIn(p_strMerchantId, sessionCookie);
|
||||
}
|
||||
|
||||
/// <summary> Holds the URL for rest calls.</summary>
|
||||
private Uri m_oCopriHost;
|
||||
|
||||
/// <summary> Spacifies name and version of app. </summary>
|
||||
private string UserAgent { get; }
|
||||
|
||||
/// <summary> Returns true because value requested form copri server are returned. </summary>
|
||||
public bool IsConnected => true;
|
||||
|
||||
/// <summary> Gets the merchant id.</summary>
|
||||
public string MerchantId => requestBuilder.MerchantId;
|
||||
|
||||
/// <summary> Gets the session cookie if user is logged in, an empty string otherwise. </summary>
|
||||
public string SessionCookie => requestBuilder.SessionCookie;
|
||||
|
||||
/// <summary> Logs user in. </summary>
|
||||
/// <param name="mailAddress">Mailaddress of user to log in.</param>
|
||||
/// <param name="password">Password to log in.</param>
|
||||
/// <param name="deviceId">Id specifying user and hardware.</param>
|
||||
/// <remarks>Response which holds auth cookie <see cref="ResponseBase.authcookie"/></remarks>
|
||||
public async Task<AuthorizationResponse> DoAuthorizationAsync(
|
||||
string mailAddress,
|
||||
string password,
|
||||
string deviceId)
|
||||
{
|
||||
return await DoAuthorizationAsync(
|
||||
m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoAuthorization(mailAddress, password, deviceId),
|
||||
() => requestBuilder.DoAuthorization(mailAddress, "********", deviceId),
|
||||
UserAgent);
|
||||
}
|
||||
|
||||
/// <summary> Logs user out. </summary>
|
||||
/// <remarks>Response which holds auth cookie <see cref="ResponseBase.authcookie"/></remarks>
|
||||
public async Task<AuthorizationoutResponse> DoAuthoutAsync()
|
||||
{
|
||||
return await DoAuthoutAsync(m_oCopriHost.AbsoluteUri, requestBuilder.DoAuthout(), UserAgent);
|
||||
}
|
||||
|
||||
/// <summary>Gets bikes available.</summary>
|
||||
/// <returns>Response holding list of bikes.</returns>
|
||||
public async Task<BikesAvailableResponse> GetBikesAvailableAsync()
|
||||
{
|
||||
return await GetBikesAvailableAsync(m_oCopriHost.AbsoluteUri, requestBuilder.GetBikesAvailable(), UserAgent);
|
||||
}
|
||||
|
||||
/// <summary> Gets a list of bikes reserved/ booked by acctive user. </summary>
|
||||
/// <returns>Response holding list of bikes.</returns>
|
||||
public async Task<BikesReservedOccupiedResponse> GetBikesOccupiedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await GetBikesOccupiedAsync(m_oCopriHost.AbsoluteUri, requestBuilder.GetBikesOccupied(), UserAgent);
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
// No user logged in.
|
||||
await Task.CompletedTask;
|
||||
return ResponseHelper.GetBikesOccupiedNone();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Get list of stations. </summary>
|
||||
/// <returns>List of files.</returns>
|
||||
public async Task<StationsAllResponse> GetStationsAsync()
|
||||
{
|
||||
return await GetStationsAsync(m_oCopriHost.AbsoluteUri, requestBuilder.GetStations(), UserAgent);
|
||||
}
|
||||
|
||||
/// <summary> Get authentication keys. </summary>
|
||||
/// <param name="bikeId">Id of the bike to get keys for.</param>
|
||||
/// <returns>Response holding authentication keys.</returns>
|
||||
public async Task<ReservationBookingResponse> GetAuthKeys(int bikeId)
|
||||
=> await GetAuthKeysAsync(m_oCopriHost.AbsoluteUri, requestBuilder.CalculateAuthKeys(bikeId), UserAgent);
|
||||
|
||||
|
||||
/// <summary> Gets booking request response.</summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Booking response.</returns>
|
||||
public async Task<ReservationBookingResponse> DoReserveAsync(
|
||||
int bikeId,
|
||||
Uri operatorUri)
|
||||
{
|
||||
return await DoReserveAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoReserve(bikeId),
|
||||
UserAgent);
|
||||
}
|
||||
|
||||
/// <summary> Gets canel booking request response.</summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on cancel booking request.</returns>
|
||||
public async Task<ReservationCancelReturnResponse> DoCancelReservationAsync(
|
||||
int bikeId,
|
||||
Uri operatorUri)
|
||||
{
|
||||
return await DoCancelReservationAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoCancelReservation(bikeId),
|
||||
UserAgent);
|
||||
}
|
||||
|
||||
/// <summary> Get authentication keys. </summary>
|
||||
/// <param name="bikeId">Id of the bike to get keys for.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response holding authentication keys.</returns>
|
||||
public async Task<ReservationBookingResponse> CalculateAuthKeysAsync(
|
||||
int bikeId,
|
||||
Uri operatorUri)
|
||||
=> await GetAuthKeysAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.CalculateAuthKeys(bikeId),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Updates lock state for a booked bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to update locking state for.</param>
|
||||
/// <param name="location">Geolocation of lock.</param>
|
||||
/// <param name="state">New locking state.</param>
|
||||
/// <param name="batteryPercentage">Holds the filling level percentage of the battery.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on updating locking state.</returns>
|
||||
public async Task<ReservationBookingResponse> UpdateLockingStateAsync(
|
||||
int bikeId,
|
||||
LocationDto location,
|
||||
lock_state state,
|
||||
double batteryLevel,
|
||||
Uri operatorUri)
|
||||
{
|
||||
return await DoUpdateLockingStateAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.UpateLockingState(bikeId, location, state, batteryLevel),
|
||||
UserAgent);
|
||||
}
|
||||
|
||||
/// <summary> Gets booking request request. </summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <param name="guid">Used to publish GUID from app to copri. Used for initial setup of bike in copri.</param>
|
||||
/// <param name="batteryPercentage">Holds the filling level percentage of the battery.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Requst on booking request.</returns>
|
||||
public async Task<ReservationBookingResponse> DoBookAsync(
|
||||
int bikeId,
|
||||
Guid guid,
|
||||
double batteryPercentage,
|
||||
Uri operatorUri)
|
||||
{
|
||||
return await DoBookAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoBook(bikeId, guid, batteryPercentage),
|
||||
UserAgent);
|
||||
}
|
||||
|
||||
/// <summary> Returns a bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>
|
||||
/// <param name="location">Geolocation of lock.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on returning request.</returns>
|
||||
public async Task<ReservationCancelReturnResponse> DoReturn(
|
||||
int bikeId,
|
||||
LocationDto location,
|
||||
Uri operatorUri)
|
||||
{
|
||||
return await DoReturn(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoReturn(bikeId, location),
|
||||
UserAgent);
|
||||
}
|
||||
|
||||
/// <summary> Submits feedback to copri server. </summary>
|
||||
/// <param name="isBikeBroken">True if bike is broken.</param>
|
||||
/// <param name="message">General purpose message or error description.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on submitting feedback request.</returns>
|
||||
public async Task<SubmitFeedbackResponse> DoSubmitFeedback(
|
||||
string message,
|
||||
bool isBikeBroken,
|
||||
Uri operatorUri) =>
|
||||
await DoSubmitFeedback(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoSubmitFeedback(message, isBikeBroken),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Logs user in. </summary>
|
||||
/// <param name="copriHost">Host to connect to. </param>
|
||||
/// <param name="command">Command to log user in.</param>
|
||||
/// <remarks>Response which holds auth cookie <see cref="ResponseBase.authcookie"/></remarks>
|
||||
public static async Task<AuthorizationResponse> DoAuthorizationAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
Func<string> displayCommand,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
/// Extract session cookie from response.
|
||||
string l_oResponseText = string.Empty;
|
||||
try
|
||||
{
|
||||
l_oResponseText = await PostAsync(
|
||||
copriHost,
|
||||
command,
|
||||
userAgent,
|
||||
displayCommand); // Do not include password into exception output when an error occurres.
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Login fehlgeschlagen aufgrund eines Netzwerkfehlers.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Login fehlgeschlagen aufgrund eines Netzwerkfehlers.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<AuthorizationResponse>>(l_oResponseText)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Logs user out. </summary>
|
||||
/// <param name="copriHost">Host to connect to. </param>
|
||||
/// <param name="command">Command to log user out.</param>
|
||||
public static async Task<AuthorizationoutResponse> DoAuthoutAsync(
|
||||
string p_strCopriHost,
|
||||
string p_oCommand,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oLogoutResponse;
|
||||
try
|
||||
{
|
||||
l_oLogoutResponse = await PostAsync(p_strCopriHost, p_oCommand, userAgent);
|
||||
}
|
||||
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Login fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Login fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
/// Extract session cookie from response.
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<AuthorizationoutResponse>>(l_oLogoutResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get list of stations from file.
|
||||
/// </summary>
|
||||
/// <param name="p_strCopriHost">URL of the copri host to connect to.</param>
|
||||
/// <param name="p_oCommand">Command to get stations.</param>
|
||||
/// <returns>List of files.</returns>
|
||||
public static async Task<StationsAllResponse> GetStationsAsync(
|
||||
string p_strCopriHost,
|
||||
string p_oCommand,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oStationsAllResponse;
|
||||
try
|
||||
{
|
||||
l_oStationsAllResponse = await PostAsync(p_strCopriHost, p_oCommand, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Abfage der verfügbaren Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Abfage der verfügbaren Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<StationsAllResponse>>(l_oStationsAllResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Gets a list of bikes from Copri. </summary>
|
||||
/// <param name="p_strCopriHost">URL of the copri host to connect to.</param>
|
||||
/// <param name="p_oCommand">Command to get bikes.</param>
|
||||
/// <returns>Response holding list of bikes.</returns>
|
||||
public static async Task<BikesAvailableResponse> GetBikesAvailableAsync(
|
||||
string p_strCopriHost,
|
||||
string l_oCommand,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oBikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
l_oBikesAvaialbeResponse = await PostAsync(p_strCopriHost, l_oCommand, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Abfage der verfügbaren Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Abfage der verfügbaren Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return CopriCallsStatic.DeserializeBikesAvailableResponse(l_oBikesAvaialbeResponse);
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Gets a list of bikes reserved/ booked by acctive user from Copri.</summary>
|
||||
/// <param name="p_strCopriHost">URL of the copri host to connect to.</param>
|
||||
/// <param name="p_oCommand">Command to post.</param>
|
||||
/// <returns>Response holding list of bikes.</returns>
|
||||
public static async Task<BikesReservedOccupiedResponse> GetBikesOccupiedAsync(
|
||||
string p_strCopriHost,
|
||||
string p_oCommand,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oBikesOccupiedResponse;
|
||||
try
|
||||
{
|
||||
l_oBikesOccupiedResponse = await PostAsync(p_strCopriHost, p_oCommand, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Abfage der reservierten/ gebuchten Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Abfage der reservierten/ gebuchten Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return CopriCallsStatic.DeserializeBikesOccupiedResponse(l_oBikesOccupiedResponse);
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Get auth keys from COPRI. </summary>
|
||||
/// <param name="copriHost">Host to connect to. </param>
|
||||
/// <param name="command">Command to log user in.</param>
|
||||
/// <returns>Response on booking request.</returns>
|
||||
public static async Task<ReservationBookingResponse> GetAuthKeysAsync(
|
||||
string p_strCopriHost,
|
||||
string p_oCommand,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oBikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
l_oBikesAvaialbeResponse = await PostAsync(p_strCopriHost, p_oCommand, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Schlosssuche wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Schlosssuche wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(l_oBikesAvaialbeResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
/// <summary> Gets booking request response. </summary>
|
||||
/// <param name="copriHost">Host to connect to. </param>
|
||||
/// <param name="command">Command to log user in.</param>
|
||||
/// <returns>Response on booking request.</returns>
|
||||
public static async Task<ReservationBookingResponse> DoReserveAsync(
|
||||
string p_strCopriHost,
|
||||
string p_oCommand,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oBikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
l_oBikesAvaialbeResponse = await PostAsync(p_strCopriHost, p_oCommand, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Reservierung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Reservierung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(l_oBikesAvaialbeResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Gets canel booking request response.</summary>
|
||||
/// <param name="copriHost">Host to connect to. </param>
|
||||
/// <param name="command">Command to log user in.</param>
|
||||
/// <returns>Response on cancel booking request.</returns>
|
||||
public static async Task<ReservationCancelReturnResponse> DoCancelReservationAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oBikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Reservierung des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Reservierung des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<ReservationCancelReturnResponse>>(l_oBikesAvaialbeResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static async Task<ReservationBookingResponse> DoUpdateLockingStateAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
string agent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oBikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, agent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Aktualisierung des Schlossstatuses wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Aktualisierung des Schlossstatuses wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(l_oBikesAvaialbeResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static async Task<ReservationBookingResponse> DoBookAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
string agent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oBikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, agent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Buchung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Buchung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(l_oBikesAvaialbeResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static async Task<ReservationCancelReturnResponse> DoReturn(
|
||||
string copriHost,
|
||||
string command,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oBikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Rückgabe des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Rückgabe des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<ReservationCancelReturnResponse>>(l_oBikesAvaialbeResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public async Task<SubmitFeedbackResponse> DoSubmitFeedback(
|
||||
string copriHost,
|
||||
string command,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string userFeedbackResponse;
|
||||
try
|
||||
{
|
||||
userFeedbackResponse = await PostAsync(copriHost, command, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Senden der Rückmeldung aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Senden der Rückmeldung aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<SubmitFeedbackResponse>>(userFeedbackResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> http get- request.</summary>
|
||||
/// <param name="Url">Ulr to get info from.</param>
|
||||
/// <returns>response from server</returns>
|
||||
public static async Task<string> Get(string Url)
|
||||
{
|
||||
string result = string.Empty;
|
||||
HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(Url);
|
||||
myRequest.Method = "GET";
|
||||
using (var myResponse = await myRequest.GetResponseAsync())
|
||||
{
|
||||
using (var sr = new StreamReader(myResponse.GetResponseStream(), Encoding.UTF8))
|
||||
{
|
||||
result = sr.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary> http- post request.</summary>
|
||||
/// <param name="p_strCommand">Command to send.</param>
|
||||
/// <param name="p_oDisplayCommand">Command to display/ log used for error handling.</param>
|
||||
/// <param name="uRL">Address of server to communicate with.</param>
|
||||
/// <returns>Response as text.</returns>
|
||||
/// <changelog> An unused member PostAsyncHttpClient using HttpClient for posting was removed 2020-04-02.</changelog>
|
||||
private static async Task<string> PostAsync(
|
||||
string uRL,
|
||||
string p_strCommand,
|
||||
string userAgent = null,
|
||||
Func<string> p_oDisplayCommand = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(p_strCommand))
|
||||
{
|
||||
Log.ForContext<CopriCallsHttps>().Fatal("Can not post command. Command must not be null or empty.");
|
||||
|
||||
throw new ArgumentException("Can not post command. Command must not be null or empty.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(uRL))
|
||||
{
|
||||
Log.ForContext<CopriCallsHttps>().Fatal("Can not post command. Host must not be null or empty.");
|
||||
|
||||
throw new ArgumentException("Can not post command. Host must not be null or empty.");
|
||||
}
|
||||
|
||||
// Get display version of command to used for display/ logging (password should never be included in output)
|
||||
Func<string> displayCommandFunc = p_oDisplayCommand ?? delegate () { return p_strCommand; };
|
||||
|
||||
try
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
var l_strHost = uRL;
|
||||
|
||||
// Returns a http request.
|
||||
var l_oRequest = WebRequest.CreateHttp(l_strHost);
|
||||
|
||||
l_oRequest.Method = "POST";
|
||||
l_oRequest.ContentType = "application/x-www-form-urlencoded";
|
||||
l_oRequest.UserAgent = userAgent;
|
||||
|
||||
// Workaround for issue https://bugzilla.xamarin.com/show_bug.cgi?id=57705
|
||||
// If not KeepAlive is set to true Stream.Write leads arbitrarily to an object disposed exception.
|
||||
l_oRequest.KeepAlive = true;
|
||||
|
||||
byte[] l_oPostData = Encoding.UTF8.GetBytes(p_strCommand);
|
||||
|
||||
l_oRequest.ContentLength = l_oPostData.Length;
|
||||
|
||||
// Get the request stream.
|
||||
using (Stream l_oDataStream = await l_oRequest.GetRequestStreamAsync())
|
||||
{
|
||||
// Write the data to the request stream.
|
||||
await l_oDataStream.WriteAsync(l_oPostData, 0, l_oPostData.Length);
|
||||
}
|
||||
|
||||
// Get the response.
|
||||
var l_oResponse = await l_oRequest.GetResponseAsync() as HttpWebResponse;
|
||||
|
||||
if (l_oResponse == null)
|
||||
{
|
||||
throw new System.Exception(string.Format("Reserve request failed. Response form from server was not of expected type."));
|
||||
}
|
||||
|
||||
if (l_oResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new CommunicationException(string.Format(
|
||||
"Posting request {0} failed. Expected status code is {1} but was {2}.",
|
||||
displayCommandFunc(),
|
||||
HttpStatusCode.OK,
|
||||
l_oResponse.StatusCode));
|
||||
}
|
||||
|
||||
string response = string.Empty;
|
||||
|
||||
// Get the request stream.
|
||||
using (Stream l_oDataStream = l_oResponse.GetResponseStream())
|
||||
using (StreamReader l_oReader = new StreamReader(l_oDataStream))
|
||||
{
|
||||
// Read the content.
|
||||
response = l_oReader.ReadToEnd();
|
||||
|
||||
// Display the content.
|
||||
Console.WriteLine(response);
|
||||
|
||||
// Clean up the streams.
|
||||
l_oResponse.Close();
|
||||
}
|
||||
|
||||
Log.ForContext<CopriCallsHttps>().Verbose("Post command {DisplayCommand} to host {URL} received {ResponseText:j}.", displayCommandFunc(), uRL, response);
|
||||
|
||||
return response;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
Log.ForContext<CopriCallsHttps>().InformationOrError("Posting command {DisplayCommand} to host {URL} failed. {Exception}.", displayCommandFunc(), uRL, l_oException);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
281
TINKLib/Repository/CopriCallsMonkeyStore.cs
Normal file
281
TINKLib/Repository/CopriCallsMonkeyStore.cs
Normal file
|
@ -0,0 +1,281 @@
|
|||
using MonkeyCache.FileStore;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Connector;
|
||||
using TINK.Model.Repository.Request;
|
||||
using TINK.Model.Repository.Response;
|
||||
using TINK.Model.Services.CopriApi;
|
||||
using TINK.Repository.Response;
|
||||
|
||||
namespace TINK.Model.Repository
|
||||
{
|
||||
public class CopriCallsMonkeyStore : ICopriCache
|
||||
{
|
||||
/// <summary> Prevents concurrent communictation. </summary>
|
||||
private object monkeyLock = new object();
|
||||
|
||||
/// <summary> Builds requests.</summary>
|
||||
private IRequestBuilder requestBuilder;
|
||||
|
||||
public const string BIKESAVAILABLE = @"{
|
||||
""copri_version"" : ""3.0.0.0"",
|
||||
""bikes"" : {},
|
||||
""response_state"" : ""OK"",
|
||||
""apiserver"" : ""https://app.tink-konstanz.de"",
|
||||
""authcookie"" : """",
|
||||
""response"" : ""bikes_available""
|
||||
}";
|
||||
|
||||
public const string BIKESOCCUPIED = @"{
|
||||
""debuglevel"" : ""1"",
|
||||
""user_id"" : """",
|
||||
""response"" : ""user_bikes_occupied"",
|
||||
""user_group"" : ""Konrad,TINK"",
|
||||
""authcookie"" : """",
|
||||
""response_state"" : ""OK"",
|
||||
""bikes_occupied"" : {},
|
||||
""copri_version"" : ""3.0.0.0"",
|
||||
""apiserver"" : ""https://app.tink-konstanz.de""
|
||||
}";
|
||||
|
||||
public const string STATIONS = @"{
|
||||
""apiserver"" : ""https://app.tink-konstanz.de"",
|
||||
""authcookie"" : """",
|
||||
""response"" : ""stations_all"",
|
||||
""copri_version"" : ""3.0.0.0"",
|
||||
""stations"" : {},
|
||||
""response_state"" : ""OK""
|
||||
}";
|
||||
|
||||
/// <summary>
|
||||
/// Holds the seconds after which station and bikes info is considered to be invalid.
|
||||
/// Default value 1s.
|
||||
/// </summary>
|
||||
private TimeSpan ExpiresAfter { get; }
|
||||
|
||||
/// <summary> Returns false because cached values are returned. </summary>
|
||||
public bool IsConnected => false;
|
||||
|
||||
/// <summary> Gets the merchant id.</summary>
|
||||
public string MerchantId => requestBuilder.MerchantId;
|
||||
|
||||
/// <summary> Gets the merchant id.</summary>
|
||||
public string SessionCookie => requestBuilder.SessionCookie;
|
||||
|
||||
/// <summary> Initializes a instance of the copri monkey store object. </summary>
|
||||
/// <param name="p_strMerchantId">Id of the merchant.</param>
|
||||
/// <param name="sessionCookie">Session cookie if user is logged in, null otherwise.</param>
|
||||
public CopriCallsMonkeyStore(
|
||||
string merchantId,
|
||||
string sessionCookie = null,
|
||||
TimeSpan? expiresAfter = null)
|
||||
{
|
||||
ExpiresAfter = expiresAfter ?? TimeSpan.FromSeconds(1);
|
||||
|
||||
requestBuilder = string.IsNullOrEmpty(sessionCookie)
|
||||
? new RequestBuilder(merchantId) as IRequestBuilder
|
||||
: new RequestBuilderLoggedIn(merchantId, sessionCookie);
|
||||
|
||||
// Ensure that store holds valid entries.
|
||||
if (!Barrel.Current.Exists(requestBuilder.GetBikesAvailable()))
|
||||
{
|
||||
AddToCache(JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE), new TimeSpan(0));
|
||||
}
|
||||
|
||||
// Do not query bikes occupied if no user is logged in (leads to not implemented exception)
|
||||
if (!string.IsNullOrEmpty(sessionCookie) && !Barrel.Current.Exists(requestBuilder.GetBikesOccupied()))
|
||||
{
|
||||
AddToCache(JsonConvert.DeserializeObject<BikesReservedOccupiedResponse>(BIKESOCCUPIED), new TimeSpan(0));
|
||||
}
|
||||
|
||||
if (!Barrel.Current.Exists(requestBuilder.GetStations()))
|
||||
{
|
||||
AddToCache(JsonConvert.DeserializeObject<StationsAllResponse>(STATIONS), new TimeSpan(0));
|
||||
}
|
||||
}
|
||||
|
||||
public Task<ReservationBookingResponse> DoReserveAsync(int bikeId, Uri operatorUri)
|
||||
{
|
||||
throw new System.Exception("Reservierung im Offlinemodus nicht möglich!");
|
||||
}
|
||||
|
||||
public Task<ReservationCancelReturnResponse> DoCancelReservationAsync(int p_iBikeId, Uri operatorUri)
|
||||
{
|
||||
throw new System.Exception("Abbrechen einer Reservierung im Offlinemodus nicht möglich!");
|
||||
}
|
||||
|
||||
public Task<ReservationBookingResponse> CalculateAuthKeysAsync(int bikeId, Uri operatorUri)
|
||||
=> throw new System.Exception("Schlosssuche im Offlinemodus nicht möglich!");
|
||||
|
||||
public Task<ReservationBookingResponse> UpdateLockingStateAsync(
|
||||
int bikeId,
|
||||
LocationDto geolocation,
|
||||
lock_state state,
|
||||
double batteryLevel,
|
||||
Uri operatorUri)
|
||||
=> throw new System.Exception("Aktualisierung des Schlossstatuses im Offlinemodus nicht möglich!");
|
||||
|
||||
public Task<ReservationBookingResponse> DoBookAsync(int bikeId, Guid guid, double batteryPercentage, Uri operatorUri)
|
||||
{
|
||||
throw new System.Exception("Buchung im Offlinemodus nicht möglich!");
|
||||
}
|
||||
|
||||
public Task<ReservationCancelReturnResponse> DoReturn(int bikeId, LocationDto geolocation, Uri operatorUri)
|
||||
{
|
||||
throw new System.Exception("Rückgabe im Offlinemodus nicht möglich!");
|
||||
}
|
||||
|
||||
public Task<SubmitFeedbackResponse> DoSubmitFeedback(string message, bool isBikeBroken, Uri operatorUri) =>
|
||||
throw new System.Exception("Übermittlung von Feedback im Offlinemodus nicht möglich!");
|
||||
|
||||
public Task<AuthorizationResponse> DoAuthorizationAsync(string p_strMailAddress, string p_strPassword, string p_strDeviceId)
|
||||
{
|
||||
throw new System.Exception("Anmelden im Offlinemodus nicht möglich!");
|
||||
}
|
||||
|
||||
public Task<AuthorizationoutResponse> DoAuthoutAsync()
|
||||
{
|
||||
throw new System.Exception("Abmelden im Offlinemodus nicht möglich!");
|
||||
}
|
||||
|
||||
public async Task<BikesAvailableResponse> GetBikesAvailableAsync()
|
||||
{
|
||||
var l_oBikesAvailableTask = new TaskCompletionSource<BikesAvailableResponse>();
|
||||
lock (monkeyLock)
|
||||
{
|
||||
l_oBikesAvailableTask.SetResult(Barrel.Current.Get<BikesAvailableResponse>(requestBuilder.GetBikesAvailable()));
|
||||
}
|
||||
return await l_oBikesAvailableTask.Task;
|
||||
}
|
||||
|
||||
public async Task<BikesReservedOccupiedResponse> GetBikesOccupiedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var l_oBikesOccupiedTask = new TaskCompletionSource<BikesReservedOccupiedResponse>();
|
||||
lock (monkeyLock)
|
||||
{
|
||||
l_oBikesOccupiedTask.SetResult(Barrel.Current.Get<BikesReservedOccupiedResponse>(requestBuilder.GetBikesOccupied()));
|
||||
}
|
||||
|
||||
return await l_oBikesOccupiedTask.Task;
|
||||
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
// No user logged in.
|
||||
await Task.CompletedTask;
|
||||
return ResponseHelper.GetBikesOccupiedNone();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<StationsAllResponse> GetStationsAsync()
|
||||
{
|
||||
var l_oStationsAllTask = new TaskCompletionSource<StationsAllResponse>();
|
||||
lock (monkeyLock)
|
||||
{
|
||||
l_oStationsAllTask.SetResult(Barrel.Current.Get<StationsAllResponse>(requestBuilder.GetStations()));
|
||||
}
|
||||
return await l_oStationsAllTask.Task;
|
||||
}
|
||||
|
||||
/// <summary> Gets a value indicating whether stations are expired or not.</summary>
|
||||
public bool IsStationsExpired
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (monkeyLock)
|
||||
{
|
||||
return Barrel.Current.IsExpired(requestBuilder.GetStations());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Adds a stations all response to cache.</summary>
|
||||
/// <param name="stations">Stations to add.</param>
|
||||
public void AddToCache(StationsAllResponse stations)
|
||||
{
|
||||
AddToCache(stations, ExpiresAfter);
|
||||
}
|
||||
|
||||
/// <summary> Adds a stations all response to cache.</summary>
|
||||
/// <param name="stations">Stations to add.</param>
|
||||
/// <param name="expiresAfter">Time after which anser is considered to be expired.</param>
|
||||
private void AddToCache(StationsAllResponse stations, TimeSpan expiresAfter)
|
||||
{
|
||||
lock (monkeyLock)
|
||||
{
|
||||
Barrel.Current.Add(
|
||||
requestBuilder.GetStations(),
|
||||
JsonConvert.SerializeObject(stations),
|
||||
expiresAfter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets a value indicating whether stations are expired or not.</summary>
|
||||
public bool IsBikesAvailableExpired
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (monkeyLock)
|
||||
{
|
||||
return Barrel.Current.IsExpired(requestBuilder.GetBikesAvailable());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Adds a bikes response to cache.</summary>
|
||||
/// <param name="bikes">Bikes to add.</param>
|
||||
public void AddToCache(BikesAvailableResponse bikes)
|
||||
{
|
||||
AddToCache(bikes, ExpiresAfter);
|
||||
}
|
||||
|
||||
/// <summary> Adds a bikes response to cache.</summary>
|
||||
/// <param name="bikes">Bikes to add.</param>
|
||||
/// <param name="expiresAfter">Time after which anser is considered to be expired.</param>
|
||||
private void AddToCache(BikesAvailableResponse bikes, TimeSpan expiresAfter)
|
||||
{
|
||||
lock (monkeyLock)
|
||||
{
|
||||
Barrel.Current.Add(
|
||||
requestBuilder.GetBikesAvailable(),
|
||||
JsonConvert.SerializeObject(bikes),
|
||||
expiresAfter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets a value indicating whether stations are expired or not.</summary>
|
||||
public bool IsBikesOccupiedExpired
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (monkeyLock)
|
||||
{
|
||||
return Barrel.Current.IsExpired(requestBuilder.GetBikesOccupied());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Adds a bikes response to cache.</summary>
|
||||
/// <param name="bikes">Bikes to add.</param>
|
||||
public void AddToCache(BikesReservedOccupiedResponse bikes)
|
||||
{
|
||||
AddToCache(bikes, ExpiresAfter);
|
||||
|
||||
}
|
||||
/// <summary> Adds a bikes response to cache.</summary>
|
||||
/// <param name="bikes">Bikes to add.</param>
|
||||
/// <param name="expiresAfter">Time after which anser is considered to be expired.</param>
|
||||
private void AddToCache(BikesReservedOccupiedResponse bikes, TimeSpan expiresAfter)
|
||||
{
|
||||
lock (monkeyLock)
|
||||
{
|
||||
Barrel.Current.Add(
|
||||
requestBuilder.GetBikesOccupied(),
|
||||
JsonConvert.SerializeObject(bikes),
|
||||
expiresAfter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
TINKLib/Repository/CopriCallsStatic.cs
Normal file
28
TINKLib/Repository/CopriCallsStatic.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using TINK.Model.Repository.Response;
|
||||
using TINK.Repository.Response;
|
||||
|
||||
namespace TINK.Model.Repository
|
||||
{
|
||||
public static class CopriCallsStatic
|
||||
{
|
||||
/// <summary>
|
||||
/// Deserializes JSON from response string.
|
||||
/// </summary>
|
||||
/// <param name="p_strResponse">Response to deserialize.</param>
|
||||
/// <returns></returns>
|
||||
public static BikesAvailableResponse DeserializeBikesAvailableResponse(string p_strResponse)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<BikesAvailableResponse>>(p_strResponse)?.tinkjson;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes JSON from response string.
|
||||
/// </summary>
|
||||
/// <param name="p_strResponse">Response to deserialize.</param>
|
||||
/// <returns></returns>
|
||||
public static BikesReservedOccupiedResponse DeserializeBikesOccupiedResponse(string p_strResponse)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<BikesReservedOccupiedResponse>>(p_strResponse)?.tinkjson;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
namespace TINK.Model.Repository.Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Is fired with reqest used a cookie which is not defined.
|
||||
/// Reasons for cookie to be not defined might be
|
||||
/// - user used more thant 8 different devices (copri invalidates cookies in this case)
|
||||
/// - user account has been deleted?
|
||||
/// </summary>
|
||||
public class AuthcookieNotDefinedException : InvalidResponseException<Response.ResponseBase>
|
||||
{
|
||||
/// <summary>Constructs a authorization exceptions. </summary>
|
||||
/// <param name="p_strTextOfAction">Text describing request which is shown if validation fails.</param>
|
||||
public AuthcookieNotDefinedException(string p_strTextOfAction, Response.ResponseBase response) :
|
||||
base($"{p_strTextOfAction}\r\nDie Sitzung ist abgelaufen. Bitte neu anmelden.", response)
|
||||
{
|
||||
}
|
||||
|
||||
public static bool IsAuthcookieNotDefined(
|
||||
Response.ResponseBase reponse,
|
||||
string actionText,
|
||||
out AuthcookieNotDefinedException exception)
|
||||
{
|
||||
if (!reponse.response_state.ToUpper().Contains(AUTH_FAILURE_QUERY_AUTHCOOKIENOTDEFIED.ToUpper())
|
||||
&& !reponse.response_state.ToUpper().Contains(AUTH_FAILURE_BOOK_AUTICOOKIENOTDEFIED.ToUpper())
|
||||
&& !reponse.response_state.ToUpper().Contains(AUTH_FAILURE_BIKESOCCUPIED_AUTICOOKIENOTDEFIED.ToUpper())
|
||||
&& !reponse.response_state.ToUpper().Contains(AUTH_FAILURE_LOGOUT_AUTHCOOKIENOTDEFIED.ToUpper()))
|
||||
{
|
||||
exception = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
exception = new AuthcookieNotDefinedException(actionText, reponse);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Holds error description if session expired. From COPRI 4.0.0.0 1001 is the only authcookie not defined error. </summary>
|
||||
private const string AUTH_FAILURE_QUERY_AUTHCOOKIENOTDEFIED = "Failure 1001: authcookie not defined";
|
||||
|
||||
/// <summary> Holds error description if session expired (Applies to COPRI < 4.0.0.0) </summary>
|
||||
private const string AUTH_FAILURE_BOOK_AUTICOOKIENOTDEFIED = "Failure 1002: authcookie not defined";
|
||||
|
||||
/// <summary> Holds error description if session expired (Applies to COPRI < 4.0.0.0) </summary>
|
||||
private const string AUTH_FAILURE_BIKESOCCUPIED_AUTICOOKIENOTDEFIED = "Failure 1003: authcookie not defined";
|
||||
|
||||
/// <summary> Holds error description if session expired. (Applies to COPRI < 4.0.0.0)</summary>
|
||||
private const string AUTH_FAILURE_LOGOUT_AUTHCOOKIENOTDEFIED = "Failure 1004: authcookie not defined";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
namespace TINK.Model.Repository.Exception
|
||||
{
|
||||
public class InvalidAuthorizationResponseException : InvalidResponseException<Response.ResponseBase>
|
||||
{
|
||||
/// <summary>Constructs a authorization exceptions. </summary>
|
||||
/// <param name="p_strMail">Mail address to create a detailed error message.</param>
|
||||
public InvalidAuthorizationResponseException(string p_strMail, Response.ResponseBase p_oResponse) :
|
||||
base(string.Format("Kann Benutzer {0} nicht anmelden. Mailadresse unbekannt oder Passwort ungültig.", p_strMail), p_oResponse)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary> Holds error description if user/ password combination is not valid. </summary>
|
||||
public const string AUTH_FAILURE_STATUS_MESSAGE_UPPERCASE = "FAILURE: CANNOT GENERATE AUTHCOOKIE";
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue