diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..145c44c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,356 @@
+# PTMagic specific
+_[Dd]oc
+_[Ss]ource
+_[Ll]ogfiles
+_[Ll]og
+_[Ll]ogs
+_[Dd]ata
+_backups
+LocalProfitTrailer
+PTMagic/settings.*.json
+Monitor/appsettings.json
+/.vscode/tasks.json
+
+
+
+# Created by https://www.gitignore.io/api/visualstudio,visualstudiocode,phpstorm
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+
+### PhpStorm ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff:
+.idea/workspace.xml
+.idea/tasks.xml
+
+# Sensitive or high-churn files:
+.idea/dataSources/
+.idea/dataSources.ids
+.idea/dataSources.xml
+.idea/dataSources.local.xml
+.idea/sqlDataSources.xml
+.idea/dynamic.xml
+.idea/uiDesigner.xml
+
+# Gradle:
+.idea/gradle.xml
+.idea/libraries
+
+# Mongo Explorer plugin:
+.idea/mongoSettings.xml
+
+## File-based project format:
+*.iws
+
+## Plugin-specific files:
+
+# IntelliJ
+/out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+### PhpStorm Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+
+### VisualStudio ###
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+*.vcxproj.filters
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/
+
+### VisualStudio Patch ###
+build/
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..4865d38
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,64 @@
+{
+ // Use IntelliSense to find out which attributes exist for C# debugging
+ // Use hover for the description of the existing attributes
+ // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "debug PTMagic",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "VS build PTMagic",
+ // If you have changed target frameworks, make sure to update the program path.
+ "program": "${workspaceFolder}/PTMagic/bin/Debug/netcoreapp2.0/PTMagic.dll",
+ "args": [],
+ "cwd": "${workspaceFolder}/PTMagic",
+ "stopAtEntry": false,
+ "internalConsoleOptions": "openOnSessionStart",
+ "env": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "sourceFileMap": {
+ "/Views": "${workspaceFolder}/Views"
+ }
+ },
+ {
+ "name": "debug Monitor",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "VS build Monitor",
+ // If you have changed target frameworks, make sure to update the program path.
+ "program": "${workspaceFolder}/Monitor/bin/Debug/netcoreapp2.0/Monitor.dll",
+ "args": [],
+ "cwd": "${workspaceFolder}/Monitor",
+ "stopAtEntry": false,
+ "internalConsoleOptions": "openOnSessionStart",
+ "launchBrowser": {
+ "enabled": true,
+ "args": "${auto-detect-url}",
+ "windows": {
+ "command": "cmd.exe",
+ "args": "/C start ${auto-detect-url}"
+ },
+ "osx": {
+ "command": "open"
+ },
+ "linux": {
+ "command": "xdg-open"
+ }
+ },
+ "env": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "sourceFileMap": {
+ "/Views": "${workspaceFolder}/Views"
+ }
+ },
+ {
+ "name": ".NET Core Attach",
+ "type": "coreclr",
+ "request": "attach",
+ "processId": "${command:pickProcess}"
+ }
+ ,]
+}
\ No newline at end of file
diff --git a/Core/Core.csproj b/Core/Core.csproj
new file mode 100644
index 0000000..48a800b
--- /dev/null
+++ b/Core/Core.csproj
@@ -0,0 +1,23 @@
+
+
+
+ netcoreapp2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/DataObjects/PTMagicData.cs b/Core/DataObjects/PTMagicData.cs
new file mode 100644
index 0000000..5f540ff
--- /dev/null
+++ b/Core/DataObjects/PTMagicData.cs
@@ -0,0 +1,528 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Text;
+
+namespace Core.Main.DataObjects.PTMagicData {
+ #region Settings Objects
+ public class GeneralSettingsWrapper {
+ public GeneralSettings GeneralSettings { get; set; }
+ }
+
+ public class AnalyzerSettingsWrapper {
+ public AnalyzerSettings AnalyzerSettings { get; set; }
+ }
+
+ public class SecureSettingsWrapper {
+ public SecureSettings SecureSettings { get; set; }
+ }
+
+ #region GeneralSettings
+ public class GeneralSettings {
+ public Application Application { get; set; }
+ public Monitor Monitor { get; set; }
+ public Backup Backup { get; set; }
+ public Telegram Telegram { get; set; }
+ }
+
+ public class Application {
+ public bool IsEnabled { get; set; } = true;
+ public bool TestMode { get; set; } = true;
+ public bool EnableBetaFeatures { get; set; } = false;
+ public int ProfitTrailerMajorVersion { get; set; } = 1;
+ public string ProfitTrailerPath { get; set; }
+ public string ProfitTrailerLicense { get; set; } = "";
+ public string ProfitTrailerMonitorURL { get; set; } = "http://localhost:8081/";
+ public string ProfitTrailerDefaultSettingName { get; set; } = "default";
+ public bool AlwaysLoadDefaultBeforeSwitch { get; set; } = true;
+ public int FloodProtectionMinutes { get; set; } = 15;
+ public string Exchange { get; set; }
+ public double StartBalance { get; set; } = 0;
+ public string InstanceName { get; set; } = "PT Magic";
+ public string TimezoneOffset { get; set; } = "+0:00";
+ public string MainFiatCurrency { get; set; } = "USD";
+ }
+
+ public class Monitor {
+ private string _rootUrl = "/";
+
+ public bool IsPasswordProtected { get; set; } = true;
+ public bool OpenBrowserOnStart { get; set; } = false;
+ public int Port { get; set; } = 5000;
+ public int GraphIntervalMinutes { get; set; } = 60;
+ public int GraphMaxTimeframeHours { get; set; } = 24;
+ public int RefreshSeconds { get; set; } = 30;
+ public int BagAnalyzerRefreshSeconds { get; set; } = 5;
+ public int BuyAnalyzerRefreshSeconds { get; set; } = 5;
+ public int MaxTopMarkets { get; set; } = 20;
+ public int MaxDailySummaries { get; set; } = 10;
+ public int MaxMonthlySummaries { get; set; } = 10;
+ public int MaxDashboardBuyEntries { get; set; } = 10;
+ public int MaxDashboardBagEntries { get; set; } = 10;
+ public int MaxDCAPairs { get; set; } = 24;
+ public int MaxSettingsLogEntries { get; set; } = 20;
+ public string LinkPlatform { get; set; } = "TradingView";
+ public string DefaultDCAMode { get; set; } = "Simple";
+
+ public string RootUrl {
+ get {
+ if (!_rootUrl.EndsWith("/")) _rootUrl += "/";
+ return _rootUrl;
+ }
+ set {
+ _rootUrl = value;
+ }
+ }
+ }
+
+ public class Backup {
+ public bool IsEnabled { get; set; } = true;
+ public int MaxHours { get; set; } = 48;
+ }
+
+ public class Telegram {
+ public bool IsEnabled { get; set; } = false;
+ public string BotToken { get; set; }
+ public Int64 ChatId { get; set; }
+ public bool SilentMode { get; set; } = false;
+ }
+ #endregion
+
+ #region AnalyzerSettings
+ public class AnalyzerSettings {
+ public MarketAnalyzer MarketAnalyzer { get; set; }
+ public List GlobalSettings { get; set; }
+ public List SingleMarketSettings { get; set; }
+ }
+
+ public class MarketAnalyzer {
+ public int StoreDataMaxHours { get; set; }
+ public int IntervalMinutes { get; set; } = 5;
+ public bool ExcludeMainCurrency { get; set; } = true;
+ public List MarketTrends { get; set; }
+ }
+
+ public class MarketTrend {
+ public string Name { get; set; }
+ public string Platform { get; set; } = "Exchange";
+
+ [DefaultValue("Market")]
+ public string TrendCurrency { get; set; } = "Market";
+
+ [DefaultValue(0)]
+ public int MaxMarkets { get; set; } = 0;
+ public int TrendMinutes { get; set; } = 0;
+
+ [DefaultValue(true)]
+ public bool DisplayGraph { get; set; } = true;
+
+ [DefaultValue(true)]
+ public bool DisplayOnMarketAnalyzerList { get; set; } = true;
+
+ [DefaultValue("")]
+ public string IgnoredMarkets { get; set; } = "";
+
+ [DefaultValue("")]
+ public string AllowedMarkets { get; set; } = "";
+
+ [DefaultValue(true)]
+ public bool ExcludeMainCurrency { get; set; } = true;
+ }
+
+ public class GlobalSetting {
+ public string SettingName { get; set; }
+ public string TriggerConnection { get; set; } = "AND";
+ public List Triggers { get; set; } = new List();
+ public Dictionary PairsProperties { get; set; } = new Dictionary();
+ public Dictionary DCAProperties { get; set; } = new Dictionary();
+ public Dictionary IndicatorsProperties { get; set; } = new Dictionary();
+ }
+
+ public class SingleMarketSetting {
+ public string SettingName { get; set; }
+ public string TriggerConnection { get; set; } = "AND";
+
+ [DefaultValue("AND")]
+ public string OffTriggerConnection { get; set; } = "AND";
+
+ [DefaultValue(true)]
+ public bool RefreshOffTriggers { get; set; } = true;
+
+ [DefaultValue("")]
+ public string IgnoredMarkets { get; set; } = "";
+
+ [DefaultValue("")]
+ public string AllowedMarkets { get; set; } = "";
+
+ [DefaultValue("")]
+ public string IgnoredGlobalSettings { get; set; } = "";
+
+ [DefaultValue("")]
+ public string AllowedGlobalSettings { get; set; } = "";
+
+ [DefaultValue(false)]
+ public bool StopProcessWhenTriggered { get; set; } = false;
+ public List Triggers { get; set; } = new List();
+ public List OffTriggers { get; set; } = new List();
+ public Dictionary PairsProperties { get; set; } = new Dictionary();
+ public Dictionary DCAProperties { get; set; } = new Dictionary();
+ public Dictionary IndicatorsProperties { get; set; } = new Dictionary();
+ }
+
+ public class Trigger {
+ [DefaultValue("")]
+ public string MarketTrendName { get; set; } = "";
+
+ [DefaultValue("Relative")]
+ public string MarketTrendRelation { get; set; } = "Relative";
+
+ [DefaultValue(Constants.MaxTrendChange)]
+ public double MaxChange { get; set; } = Constants.MaxTrendChange;
+
+ [DefaultValue(Constants.MinTrendChange)]
+ public double MinChange { get; set; } = Constants.MinTrendChange;
+
+ [DefaultValue(Constants.Max24hVolume)]
+ public double Max24hVolume { get; set; } = Constants.Max24hVolume;
+
+ [DefaultValue(0.0)]
+ public double Min24hVolume { get; set; } = 0.0;
+
+ [DefaultValue(0)]
+ public int AgeDaysLowerThan { get; set; } = 0;
+ }
+
+ public class OffTrigger {
+ [DefaultValue("")]
+ public string MarketTrendName { get; set; } = "";
+
+ [DefaultValue("Relative")]
+ public string MarketTrendRelation { get; set; } = "Relative";
+
+ [DefaultValue(Constants.MaxTrendChange)]
+ public double MaxChange { get; set; } = Constants.MaxTrendChange;
+
+ [DefaultValue(Constants.MinTrendChange)]
+ public double MinChange { get; set; } = Constants.MinTrendChange;
+
+ [DefaultValue(Constants.Max24hVolume)]
+ public double Max24hVolume { get; set; } = Constants.Max24hVolume;
+
+ [DefaultValue(0.0)]
+ public double Min24hVolume { get; set; } = 0.0;
+
+ [DefaultValue(0)]
+ public int HoursSinceTriggered { get; set; } = 0;
+ }
+ #endregion
+
+ #region SecureSettings
+ public class SecureSettings {
+ public string MonitorPassword { get; set; } = "";
+ }
+ #endregion
+
+ #endregion
+
+ #region Market Analyzer Objects
+ public class Market {
+ public int Position;
+ public string Name = "";
+ public string Symbol = "";
+ public double Volume24h = 0.0;
+ public double Price = 0.0;
+ public double TrendChange24h = 0.0;
+ public double MainCurrencyPriceUSD = 0.0;
+ }
+
+ public class MarketTick {
+ public double Volume24h = 0.0;
+ public double Price = 0.0;
+ public DateTime Time = Constants.confMinDate;
+ }
+
+ public class MarketTrendChange {
+ public string MarketTrendName = "";
+ public string Market = "";
+ public double LastPrice = 0.0;
+ public double Volume24h = 0.0;
+ public double TrendMinutes = 0.0;
+ public double TrendChange = 0.0;
+ public DateTime TrendDateTime = Constants.confMinDate;
+ }
+
+ public class MarketInfo {
+ public string Name = "";
+ public DateTime FirstSeen = Constants.confMinDate;
+ public DateTime LastSeen = Constants.confMaxDate;
+ }
+ #endregion
+
+ #region Summary Objects
+ public class Summary {
+ public string Version { get; set; } = "";
+ public DateTime LastRuntime { get; set; } = Constants.confMinDate;
+ public int LastRuntimeSeconds { get; set; } = 0;
+ public DateTime LastGlobalSettingSwitch { get; set; } = Constants.confMinDate;
+ public GlobalSetting CurrentGlobalSetting { get; set; } = null;
+ public GlobalSetting FloodProtectedSetting { get; set; } = null;
+ public bool IsSOMActive { get; set; } = false;
+ public Dictionary MarketSummary { get; set; } = new Dictionary();
+ public Dictionary> MarketTrendChanges { get; set; } = new Dictionary>();
+ public List GlobalSettingSummary { get; set; } = new List();
+ public double BuyValue { get; set; } = 0;
+ public double TrailingBuy { get; set; } = 0;
+ public double SellValue { get; set; } = 0;
+ public double TrailingProfit { get; set; } = 0;
+ public double MaxTradingPairs { get; set; } = 0;
+ public double MaxCost { get; set; } = 0;
+ public double MaxCostPercentage { get; set; } = 0;
+ public double MinBuyVolume { get; set; } = 0;
+ public double DCALevels { get; set; } = 0;
+ public double DCATrigger { get; set; } = 0;
+ public Dictionary DCATriggers { get; set; } = new Dictionary();
+ public double DCAPercentage { get; set; } = 0;
+ public Dictionary DCAPercentages { get; set; } = new Dictionary();
+ public string DCABuyStrategy { get; set; } = "";
+ public string BuyStrategy { get; set; } = "";
+ public string SellStrategy { get; set; } = "";
+ public string MainMarket { get; set; } = "";
+ public double MainMarketPrice { get; set; } = 0;
+ public string MainFiatCurrency { get; set; } = "USD";
+ public double MainFiatCurrencyExchangeRate { get; set; } = 1;
+ public int ProfitTrailerMajorVersion { get; set; } = 1;
+ public List BuyStrategies { get; set; } = new List();
+ public List SellStrategies { get; set; } = new List();
+ public List DCABuyStrategies { get; set; } = new List();
+ public List DCASellStrategies { get; set; } = new List();
+ }
+
+ public class StrategySummary {
+ public string Name { get; set; } = "";
+ public double Value { get; set; } = 0;
+ }
+
+ public class GlobalSettingSummary {
+ public string SettingName { get; set; }
+ public DateTime SwitchDateTime { get; set; }
+ public int ActiveSeconds { get; set; } = 0;
+ public Dictionary MarketTrendChanges { get; set; } = new Dictionary();
+ }
+
+ public class MarketPairSummary {
+ public bool IsTradingEnabled { get; set; } = false;
+ public bool IsSOMActive { get; set; } = false;
+ public bool IsDCAEnabled { get; set; } = false;
+ public List ActiveSingleSettings { get; set; } = null;
+ public double CurrentBuyValue { get; set; } = 0;
+ public double CurrentTrailingBuy { get; set; } = 0;
+ public double CurrentSellValue { get; set; } = 0;
+ public double CurrentTrailingProfit { get; set; } = 0;
+ public double LatestPrice { get; set; } = 0;
+ public double Latest24hVolume { get; set; } = 0;
+ public Dictionary MarketTrendChanges { get; set; } = new Dictionary();
+ public List BuyStrategies { get; set; } = new List();
+ public List SellStrategies { get; set; } = new List();
+ public List DCABuyStrategies { get; set; } = new List();
+ public List DCASellStrategies { get; set; } = new List();
+ }
+ #endregion
+
+ #region Transaction Objects
+ public class Transaction {
+ public string GUID { get; set; } = "";
+ public DateTime UTCDateTime { get; set; } = Constants.confMinDate;
+ public double Amount { get; set; } = 0.0;
+
+ public DateTime GetLocalDateTime(string offset) {
+ DateTimeOffset result = this.UTCDateTime;
+
+ // Convert UTC sales time to local offset time
+ TimeSpan offsetTimeSpan = TimeSpan.Parse(offset.Replace("+", ""));
+ result = result.ToOffset(offsetTimeSpan);
+
+ return result.DateTime;
+ }
+ }
+ #endregion
+
+ #region SingleMarketSettingSummary Objects
+ public class SingleMarketSettingSummary {
+ public string Market { get; set; } = "";
+ public DateTime ActivationDateTimeUTC { get; set; } = Constants.confMinDate;
+ public SingleMarketSetting SingleMarketSetting { get; set; } = null;
+ public TriggerSnapshot TriggerSnapshot { get; set; } = null;
+ }
+
+ public class TriggerSnapshot {
+ public Dictionary RelevantTriggers { get; set; } = new Dictionary();
+ public List MatchedTriggersContent { get; set; } = new List();
+ public double LastPrice { get; set; } = 0;
+ public double Last24hVolume { get; set; } = 0;
+ }
+ #endregion
+
+ #region Profit Trailer JSON Objects
+
+ public class PTData {
+ public List SellLogData { get; set; } = new List();
+ public List DCALogData { get; set; } = new List();
+ public List GainLogData { get; set; } = new List();
+ public List bbBuyLogData { get; set; } = new List();
+ }
+
+ public class sellLogData {
+ public double soldAmount { get; set; }
+ public SoldDate soldDate { get; set; }
+ public int boughtTimes { get; set; }
+ public string market { get; set; }
+ public double profit { get; set; }
+ public AverageCalculator averageCalculator { get; set; }
+ public double currentPrice { get; set; }
+ }
+
+ public class SellLogData {
+ public double SoldAmount { get; set; }
+ public DateTime SoldDate { get; set; }
+ public int BoughtTimes { get; set; }
+ public string Market { get; set; }
+ public double ProfitPercent { get; set; }
+ public double Profit { get; set; }
+ public double AverageBuyPrice { get; set; }
+ public double TotalCost { get; set; }
+ public double SoldPrice { get; set; }
+ public double SoldValue { get; set; }
+ }
+
+ public class SoldDate {
+ public Date date { get; set; }
+ public Time time { get; set; }
+ }
+
+ public class FirstBoughtDate {
+ public Date date { get; set; }
+ public Time time { get; set; }
+ }
+
+ public class Date {
+ public int year { get; set; }
+ public int month { get; set; }
+ public int day { get; set; }
+ }
+
+ public class Time {
+ public int hour { get; set; }
+ public int minute { get; set; }
+ public int second { get; set; }
+ public int nano { get; set; }
+ }
+
+ public class AverageCalculator {
+ public double totalCost { get; set; }
+ public double totalAmount { get; set; }
+ public double totalAmountWithSold { get; set; }
+ public double avgPrice { get; set; }
+ public double avgCost { get; set; }
+ public FirstBoughtDate firstBoughtDate { get; set; }
+ public double totalWeightedPrice { get; set; }
+ public double orderNumber { get; set; }
+ public double fee { get; set; }
+ }
+
+ public class PTStrategy {
+ public string type { get; set; }
+ public string name { get; set; }
+ public double entryValue { get; set; }
+ public double entryValueLimit { get; set; }
+ public double triggerValue { get; set; }
+ public double currentValue { get; set; }
+ public double currentValuePercentage { get; set; }
+ public int decimals { get; set; }
+ public string positive { get; set; }
+ }
+
+ public class dcaLogData {
+ public int boughtTimes { get; set; } = 0;
+ public string market { get; set; }
+ public string positive { get; set; }
+ public double buyProfit { get; set; }
+ public double BBLow { get; set; }
+ public double BBTrigger { get; set; }
+ public double highbb { get; set; }
+ public double profit { get; set; }
+ public AverageCalculator averageCalculator { get; set; }
+ public double currentPrice { get; set; }
+ public string sellStrategy { get; set; }
+ public string buyStrategy { get; set; }
+ public double triggerValue { get; set; }
+ public double percChange { get; set; }
+ public List buyStrategies { get; set; }
+ public List sellStrategies { get; set; }
+ }
+
+ public class Strategy {
+ public string Type { get; set; }
+ public string Name { get; set; }
+ public double EntryValue { get; set; }
+ public double EntryValueLimit { get; set; }
+ public double TriggerValue { get; set; }
+ public double CurrentValue { get; set; }
+ public double CurrentValuePercentage { get; set; }
+ public int Decimals { get; set; }
+ public bool IsTrailing { get; set; }
+ }
+
+ public class DCALogData {
+ public int BoughtTimes { get; set; }
+ public double CurrentLowBBValue { get; set; }
+ public double CurrentHighBBValue { get; set; }
+ public double BBTrigger { get; set; }
+ public double BuyTriggerPercent { get; set; }
+ public bool IsTrailing { get; set; }
+ public string Market { get; set; }
+ public double ProfitPercent { get; set; }
+ public double AverageBuyPrice { get; set; }
+ public double TotalCost { get; set; }
+ public double Amount { get; set; }
+ public double CurrentPrice { get; set; }
+ public double SellTrigger { get; set; }
+ public double PercChange { get; set; }
+ public DateTime FirstBoughtDate { get; set; }
+ public string SellStrategy { get; set; }
+ public string BuyStrategy { get; set; }
+ public List BuyStrategies { get; set; } = new List();
+ public List SellStrategies { get; set; } = new List();
+ }
+
+ public class buyLogData {
+ public string market { get; set; }
+ public string positive { get; set; }
+ public double BBLow { get; set; }
+ public double BBHigh { get; set; }
+ public double BBTrigger { get; set; }
+ public double profit { get; set; }
+ public double currentPrice { get; set; }
+ public double currentValue { get; set; }
+ public string buyStrategy { get; set; }
+ public double triggerValue { get; set; }
+ public double percChange { get; set; }
+ public List buyStrategies { get; set; }
+ }
+
+ public class BuyLogData {
+ public double CurrentLowBBValue { get; set; }
+ public double CurrentHighBBValue { get; set; }
+ public double BBTrigger { get; set; }
+ public bool IsTrailing { get; set; }
+ public string Market { get; set; }
+ public double ProfitPercent { get; set; }
+ public double CurrentPrice { get; set; }
+ public double CurrentValue { get; set; }
+ public double TriggerValue { get; set; }
+ public double PercChange { get; set; }
+ public string BuyStrategy { get; set; }
+ public List BuyStrategies { get; set; } = new List();
+ }
+
+ #endregion
+}
diff --git a/Core/DataObjects/ProfitTrailerData.cs b/Core/DataObjects/ProfitTrailerData.cs
new file mode 100644
index 0000000..0f8710e
--- /dev/null
+++ b/Core/DataObjects/ProfitTrailerData.cs
@@ -0,0 +1,298 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Newtonsoft.Json;
+using Core.Main.DataObjects.PTMagicData;
+
+namespace Core.Main.DataObjects {
+
+ public class ProfitTrailerData {
+ private List _sellLog = new List();
+ private List _dcaLog = new List();
+ private List _buyLog = new List();
+ private string _ptmBasePath = "";
+ private PTMagicConfiguration _systemConfiguration = null;
+ private TransactionData _transactionData = null;
+ private DateTimeOffset _dateTimeNow = Constants.confMinDate;
+
+ public ProfitTrailerData(string ptmBasePath, PTMagicConfiguration systemConfiguration) {
+ _ptmBasePath = ptmBasePath;
+ _systemConfiguration = systemConfiguration;
+
+ PTData rawPTData = JsonConvert.DeserializeObject(File.ReadAllText(systemConfiguration.GeneralSettings.Application.ProfitTrailerPath + "ProfitTrailerData.json"));
+ if (rawPTData.SellLogData != null) {
+ this.BuildSellLogData(rawPTData.SellLogData, _systemConfiguration);
+ }
+
+ if (rawPTData.bbBuyLogData != null) {
+ this.BuildBuyLogData(rawPTData.bbBuyLogData, _systemConfiguration);
+ }
+
+ if (rawPTData.DCALogData != null) {
+ this.BuildDCALogData(rawPTData.DCALogData, rawPTData.GainLogData, _systemConfiguration);
+ }
+
+ // Convert local offset time to UTC
+ TimeSpan offsetTimeSpan = TimeSpan.Parse(systemConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
+ _dateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan);
+ }
+
+ public List SellLog {
+ get {
+ return _sellLog;
+ }
+ }
+
+ public List SellLogToday {
+ get {
+ return _sellLog.FindAll(sl => sl.SoldDate.Date == _dateTimeNow.DateTime.Date);
+ }
+ }
+
+ public List SellLogYesterday {
+ get {
+ return _sellLog.FindAll(sl => sl.SoldDate.Date == _dateTimeNow.DateTime.AddDays(-1).Date);
+ }
+ }
+
+ public List SellLogLast7Days {
+ get {
+ return _sellLog.FindAll(sl => sl.SoldDate.Date >= _dateTimeNow.DateTime.AddDays(-7).Date);
+ }
+ }
+
+ public List DCALog {
+ get {
+ return _dcaLog;
+ }
+ }
+
+ public List BuyLog {
+ get {
+ return _buyLog;
+ }
+ }
+
+ public TransactionData TransactionData {
+ get {
+ if (_transactionData == null) _transactionData = new TransactionData(_ptmBasePath);
+ return _transactionData;
+ }
+ }
+
+ public double GetCurrentBalance() {
+ return this.GetSnapshotBalance(DateTime.Now.ToUniversalTime());
+ }
+
+ public double GetSnapshotBalance(DateTime snapshotDateTime) {
+ double result = _systemConfiguration.GeneralSettings.Application.StartBalance;
+
+ result += this.SellLog.FindAll(sl => sl.SoldDate.Date < snapshotDateTime.Date).Sum(sl => sl.Profit);
+ result += this.TransactionData.Transactions.FindAll(t => t.GetLocalDateTime(_systemConfiguration.GeneralSettings.Application.TimezoneOffset) < snapshotDateTime).Sum(t => t.Amount);
+
+ return result;
+ }
+
+ private void BuildSellLogData(List rawSellLogData, PTMagicConfiguration systemConfiguration) {
+ foreach (sellLogData rsld in rawSellLogData) {
+ SellLogData sellLogData = new SellLogData();
+ sellLogData.SoldAmount = rsld.soldAmount;
+ sellLogData.BoughtTimes = rsld.boughtTimes;
+ sellLogData.Market = rsld.market;
+ sellLogData.ProfitPercent = rsld.profit;
+ sellLogData.SoldPrice = rsld.currentPrice;
+ sellLogData.AverageBuyPrice = rsld.averageCalculator.avgPrice;
+ sellLogData.TotalCost = sellLogData.SoldAmount * sellLogData.AverageBuyPrice;
+
+ double soldValueRaw = (sellLogData.SoldAmount * sellLogData.SoldPrice);
+ double soldValueAfterFees = soldValueRaw - (soldValueRaw * (rsld.averageCalculator.fee / 100));
+ sellLogData.SoldValue = soldValueAfterFees;
+ sellLogData.Profit = Math.Round(sellLogData.SoldValue - sellLogData.TotalCost, 8);
+
+ // Profit Trailer sales are saved in UTC
+ DateTimeOffset ptSoldDate = DateTimeOffset.Parse(rsld.soldDate.date.year.ToString() + "-" + rsld.soldDate.date.month.ToString("00") + "-" + rsld.soldDate.date.day.ToString("00") + "T" + rsld.soldDate.time.hour.ToString("00") + ":" + rsld.soldDate.time.minute.ToString("00") + ":" + rsld.soldDate.time.second.ToString("00"), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
+
+ // Convert UTC sales time to local offset time
+ TimeSpan offsetTimeSpan = TimeSpan.Parse(systemConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
+ ptSoldDate = ptSoldDate.ToOffset(offsetTimeSpan);
+
+ sellLogData.SoldDate = ptSoldDate.DateTime;
+
+ _sellLog.Add(sellLogData);
+ }
+ }
+
+ private void BuildDCALogData(List rawDCALogData, List rawPairsLogData, PTMagicConfiguration systemConfiguration) {
+ foreach (dcaLogData rdld in rawDCALogData) {
+ DCALogData dcaLogData = new DCALogData();
+ dcaLogData.Amount = rdld.averageCalculator.totalAmount;
+ dcaLogData.BoughtTimes = rdld.boughtTimes;
+ dcaLogData.Market = rdld.market;
+ dcaLogData.ProfitPercent = rdld.profit;
+ dcaLogData.AverageBuyPrice = rdld.averageCalculator.avgPrice;
+ dcaLogData.TotalCost = rdld.averageCalculator.totalCost;
+ dcaLogData.BuyTriggerPercent = rdld.buyProfit;
+ dcaLogData.CurrentLowBBValue = rdld.BBLow;
+ dcaLogData.CurrentHighBBValue = rdld.highbb;
+ dcaLogData.BBTrigger = rdld.BBTrigger;
+ dcaLogData.CurrentPrice = rdld.currentPrice;
+ dcaLogData.SellTrigger = rdld.triggerValue;
+ dcaLogData.PercChange = rdld.percChange;
+ dcaLogData.BuyStrategy = rdld.buyStrategy;
+ if (dcaLogData.BuyStrategy == null) dcaLogData.BuyStrategy = "";
+ dcaLogData.SellStrategy = rdld.sellStrategy;
+ if (dcaLogData.SellStrategy == null) dcaLogData.SellStrategy = "";
+
+ if (rdld.positive != null) {
+ dcaLogData.IsTrailing = rdld.positive.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) > -1;
+ } else {
+ if (rdld.buyStrategies != null) {
+ foreach (PTStrategy bs in rdld.buyStrategies) {
+ Strategy buyStrategy = new Strategy();
+ buyStrategy.Type = bs.type;
+ buyStrategy.Name = bs.name;
+ buyStrategy.EntryValue = bs.entryValue;
+ buyStrategy.EntryValueLimit = bs.entryValueLimit;
+ buyStrategy.TriggerValue = bs.triggerValue;
+ buyStrategy.CurrentValue = bs.currentValue;
+ buyStrategy.CurrentValuePercentage = bs.currentValuePercentage;
+ buyStrategy.Decimals = bs.decimals;
+ buyStrategy.IsTrailing = bs.positive.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) > -1;
+
+ dcaLogData.BuyStrategies.Add(buyStrategy);
+ }
+ }
+
+ if (rdld.sellStrategies != null) {
+ foreach (PTStrategy ss in rdld.sellStrategies) {
+ Strategy sellStrategy = new Strategy();
+ sellStrategy.Type = ss.type;
+ sellStrategy.Name = ss.name;
+ sellStrategy.EntryValue = ss.entryValue;
+ sellStrategy.EntryValueLimit = ss.entryValueLimit;
+ sellStrategy.TriggerValue = ss.triggerValue;
+ sellStrategy.CurrentValue = ss.currentValue;
+ sellStrategy.CurrentValuePercentage = ss.currentValuePercentage;
+ sellStrategy.Decimals = ss.decimals;
+ sellStrategy.IsTrailing = ss.positive.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) > -1;
+
+ dcaLogData.SellStrategies.Add(sellStrategy);
+ }
+ }
+ }
+
+
+ // Profit Trailer bought times are saved in UTC
+ if (rdld.averageCalculator.firstBoughtDate != null) {
+ DateTimeOffset ptFirstBoughtDate = DateTimeOffset.Parse(rdld.averageCalculator.firstBoughtDate.date.year.ToString() + "-" + rdld.averageCalculator.firstBoughtDate.date.month.ToString("00") + "-" + rdld.averageCalculator.firstBoughtDate.date.day.ToString("00") + "T" + rdld.averageCalculator.firstBoughtDate.time.hour.ToString("00") + ":" + rdld.averageCalculator.firstBoughtDate.time.minute.ToString("00") + ":" + rdld.averageCalculator.firstBoughtDate.time.second.ToString("00"), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
+
+ // Convert UTC bought time to local offset time
+ TimeSpan offsetTimeSpan = TimeSpan.Parse(systemConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
+ ptFirstBoughtDate = ptFirstBoughtDate.ToOffset(offsetTimeSpan);
+
+ dcaLogData.FirstBoughtDate = ptFirstBoughtDate.DateTime;
+ } else {
+ dcaLogData.FirstBoughtDate = Constants.confMinDate;
+ }
+
+ _dcaLog.Add(dcaLogData);
+ }
+
+ foreach (dcaLogData rpld in rawPairsLogData) {
+ DCALogData dcaLogData = new DCALogData();
+ dcaLogData.Amount = rpld.averageCalculator.totalAmount;
+ dcaLogData.BoughtTimes = 0;
+ dcaLogData.Market = rpld.market;
+ dcaLogData.ProfitPercent = rpld.profit;
+ dcaLogData.AverageBuyPrice = rpld.averageCalculator.avgPrice;
+ dcaLogData.TotalCost = rpld.averageCalculator.totalCost;
+ dcaLogData.BuyTriggerPercent = rpld.buyProfit;
+ dcaLogData.CurrentPrice = rpld.currentPrice;
+ dcaLogData.SellTrigger = rpld.triggerValue;
+ dcaLogData.PercChange = rpld.percChange;
+ dcaLogData.BuyStrategy = rpld.buyStrategy;
+ if (dcaLogData.BuyStrategy == null) dcaLogData.BuyStrategy = "";
+ dcaLogData.SellStrategy = rpld.sellStrategy;
+ if (dcaLogData.SellStrategy == null) dcaLogData.SellStrategy = "";
+ dcaLogData.IsTrailing = false;
+
+ if (rpld.sellStrategies != null) {
+ foreach (PTStrategy ss in rpld.sellStrategies) {
+ Strategy sellStrategy = new Strategy();
+ sellStrategy.Type = ss.type;
+ sellStrategy.Name = ss.name;
+ sellStrategy.EntryValue = ss.entryValue;
+ sellStrategy.EntryValueLimit = ss.entryValueLimit;
+ sellStrategy.TriggerValue = ss.triggerValue;
+ sellStrategy.CurrentValue = ss.currentValue;
+ sellStrategy.CurrentValuePercentage = ss.currentValuePercentage;
+ sellStrategy.Decimals = ss.decimals;
+ sellStrategy.IsTrailing = ss.positive.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) > -1;
+
+ dcaLogData.SellStrategies.Add(sellStrategy);
+ }
+ }
+
+ // Profit Trailer bought times are saved in UTC
+ if (rpld.averageCalculator.firstBoughtDate != null) {
+ DateTimeOffset ptFirstBoughtDate = DateTimeOffset.Parse(rpld.averageCalculator.firstBoughtDate.date.year.ToString() + "-" + rpld.averageCalculator.firstBoughtDate.date.month.ToString("00") + "-" + rpld.averageCalculator.firstBoughtDate.date.day.ToString("00") + "T" + rpld.averageCalculator.firstBoughtDate.time.hour.ToString("00") + ":" + rpld.averageCalculator.firstBoughtDate.time.minute.ToString("00") + ":" + rpld.averageCalculator.firstBoughtDate.time.second.ToString("00"), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
+
+ // Convert UTC bought time to local offset time
+ TimeSpan offsetTimeSpan = TimeSpan.Parse(systemConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
+ ptFirstBoughtDate = ptFirstBoughtDate.ToOffset(offsetTimeSpan);
+
+ dcaLogData.FirstBoughtDate = ptFirstBoughtDate.DateTime;
+ } else {
+ dcaLogData.FirstBoughtDate = Constants.confMinDate;
+ }
+
+ _dcaLog.Add(dcaLogData);
+ }
+ }
+
+ private void BuildBuyLogData(List rawBuyLogData, PTMagicConfiguration systemConfiguration) {
+ foreach (buyLogData rbld in rawBuyLogData) {
+ BuyLogData buyLogData = new BuyLogData();
+ buyLogData.Market = rbld.market;
+ buyLogData.ProfitPercent = rbld.profit;
+ buyLogData.TriggerValue = rbld.triggerValue;
+ buyLogData.CurrentValue = rbld.currentValue;
+ buyLogData.CurrentPrice = rbld.currentPrice;
+ buyLogData.PercChange = rbld.percChange;
+ buyLogData.BuyStrategy = rbld.buyStrategy;
+ buyLogData.CurrentLowBBValue = rbld.BBLow;
+ buyLogData.CurrentHighBBValue = rbld.BBHigh;
+ buyLogData.BBTrigger = rbld.BBTrigger;
+
+ if (buyLogData.BuyStrategy == null) buyLogData.BuyStrategy = "";
+
+ if (rbld.positive != null) {
+ buyLogData.IsTrailing = rbld.positive.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) > -1;
+ } else {
+ if (rbld.buyStrategies != null) {
+ foreach (PTStrategy bs in rbld.buyStrategies) {
+ Strategy buyStrategy = new Strategy();
+ buyStrategy.Type = bs.type;
+ buyStrategy.Name = bs.name;
+ buyStrategy.EntryValue = bs.entryValue;
+ buyStrategy.EntryValueLimit = bs.entryValueLimit;
+ buyStrategy.TriggerValue = bs.triggerValue;
+ buyStrategy.CurrentValue = bs.currentValue;
+ buyStrategy.CurrentValuePercentage = bs.currentValuePercentage;
+ buyStrategy.Decimals = bs.decimals;
+ buyStrategy.IsTrailing = bs.positive.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) > -1;
+
+ buyLogData.BuyStrategies.Add(buyStrategy);
+ }
+ }
+ }
+
+ _buyLog.Add(buyLogData);
+ }
+ }
+ }
+}
diff --git a/Core/DataObjects/TransactionData.cs b/Core/DataObjects/TransactionData.cs
new file mode 100644
index 0000000..7332355
--- /dev/null
+++ b/Core/DataObjects/TransactionData.cs
@@ -0,0 +1,33 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Globalization;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Newtonsoft.Json;
+using Core.Main.DataObjects.PTMagicData;
+using Core.Helper;
+
+namespace Core.Main.DataObjects {
+
+ public class TransactionData {
+ private List _transactions = new List();
+
+ public TransactionData(string basePath) {
+ string transactionsFilePath = basePath + Constants.PTMagicPathData + Path.DirectorySeparatorChar + "Transactions.json";
+ if (File.Exists(transactionsFilePath)) {
+ this._transactions = JsonConvert.DeserializeObject>(File.ReadAllText(transactionsFilePath));
+ }
+ }
+
+ public List Transactions {
+ get {
+ return _transactions;
+ }
+ }
+
+ public void SaveTransactions(string basePath) {
+ FileHelper.WriteTextToFile(basePath + Constants.PTMagicPathData + Path.DirectorySeparatorChar, "Transactions.json", JsonConvert.SerializeObject(this.Transactions));
+ }
+ }
+}
diff --git a/Core/Helper/CertificateHelper.cs b/Core/Helper/CertificateHelper.cs
new file mode 100644
index 0000000..636b78c
--- /dev/null
+++ b/Core/Helper/CertificateHelper.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.IO;
+using System.Net;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Core.Helper {
+ public static class CertificateHelper {
+
+ public static bool AllwaysGoodCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors policyErrors) {
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Core/Helper/EncryptionHelper.cs b/Core/Helper/EncryptionHelper.cs
new file mode 100644
index 0000000..99a8368
--- /dev/null
+++ b/Core/Helper/EncryptionHelper.cs
@@ -0,0 +1,303 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.IO;
+using System.Text;
+using System.Security.Cryptography;
+using System.Collections.Specialized;
+using System.Configuration;
+
+namespace Core.Helper {
+ public class EncryptionHelper {
+
+ #region Properties
+
+ public static string CryptoMainSaltValue {
+ get {
+ return "b3+Pz.~L2EK>((/xnTbWdTo:/5_$hq8ja8yOq% j}M6zTM";
+ }
+ }
+
+ #endregion
+
+ #region Methoden
+
+ #region Passwortverschlüsselung
+ public static string CreateHash(string password, string randomSalt) {
+ // Generate a random salt
+ byte[] salt = Encoding.UTF8.GetBytes(EncryptionHelper.CryptoMainSaltValue + randomSalt);
+ byte[] hash = PBKDF2(password, salt, 64000, 24);
+
+ return Convert.ToBase64String(hash);
+ }
+
+ public static bool SlowEquals(string aHash, string bHash) {
+ byte[] a = Encoding.UTF8.GetBytes(aHash);
+ byte[] b = Encoding.UTF8.GetBytes(bHash);
+
+ uint diff = (uint)a.Length ^ (uint)b.Length;
+ for (int i = 0; i < a.Length && i < b.Length; i++) {
+ diff |= (uint)(a[i] ^ b[i]);
+ }
+ return diff == 0;
+ }
+
+ private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes) {
+ using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt)) {
+ pbkdf2.IterationCount = iterations;
+ return pbkdf2.GetBytes(outputBytes);
+ }
+ }
+
+
+ #endregion
+
+ #region Standardverschlüsselung
+ public static string Encrypt(string plainText) {
+ return Encrypt(plainText, EncryptionHelper.CryptoPassPhrase, EncryptionHelper.CryptoSaltValue, "SHA512", 2, EncryptionHelper.CryptoInitVector, 256);
+ }
+
+ public static string Decrypt(string cipherText) {
+ return Decrypt(cipherText, EncryptionHelper.CryptoPassPhrase, EncryptionHelper.CryptoSaltValue, "SHA512", 2, EncryptionHelper.CryptoInitVector, 256, true);
+ }
+
+ public static string Encrypt(string plainText, string passPhrase) {
+ return Encrypt(plainText, passPhrase, EncryptionHelper.CryptoSaltValue, "SHA512", 2, EncryptionHelper.CryptoInitVector, 256);
+ }
+
+ public static string Decrypt(string cipherText, string passPhrase) {
+ return Decrypt(cipherText, passPhrase, EncryptionHelper.CryptoSaltValue, "SHA512", 2, EncryptionHelper.CryptoInitVector, 256, true);
+ }
+
+
+ ///
+ /// Encrypts specified plaintext using Rijndael symmetric key algorithm
+ /// and returns a base64-encoded result.
+ ///
+ ///
+ /// Plaintext value to be encrypted.
+ ///
+ ///
+ /// Passphrase from which a pseudo-random password will be derived. The
+ /// derived password will be used to generate the encryption key.
+ /// Passphrase can be any string. In this example we assume that this
+ /// passphrase is an ASCII string.
+ ///
+ ///
+ /// Salt value used along with passphrase to generate password. Salt can
+ /// be any string. In this example we assume that salt is an ASCII string.
+ ///
+ ///
+ /// Hash algorithm used to generate password. Allowed values are: "MD5" and
+ /// "SHA1". SHA1 hashes are a bit slower, but more secure than MD5 hashes.
+ ///
+ ///
+ /// Number of iterations used to generate password. One or two iterations
+ /// should be enough.
+ ///
+ ///
+ /// Initialization vector (or IV). This value is required to encrypt the
+ /// first block of plaintext data. For RijndaelManaged class IV must be
+ /// exactly 16 ASCII characters long.
+ ///
+ ///
+ /// Size of encryption key in bits. Allowed values are: 128, 192, and 256.
+ /// Longer keys are more secure than shorter keys.
+ ///
+ ///
+ /// Encrypted value formatted as a base64-encoded string.
+ ///
+ public static string Encrypt(string plainText,
+ string passPhrase,
+ string saltValue,
+ string hashAlgorithm,
+ int passwordIterations,
+ string initVector,
+ int keySize) {
+ // Convert strings into byte arrays.
+ byte[] initVectorBytes = Encoding.UTF8.GetBytes(initVector);
+ byte[] saltValueBytes = Encoding.UTF8.GetBytes(saltValue);
+
+ // Convert our plaintext into a byte array.
+ // Let us assume that plaintext contains UTF8-encoded characters.
+ byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
+
+ // First, we must create a password, from which the key will be derived.
+ // This password will be generated from the specified passphrase and
+ // salt value. The password will be created using the specified hash
+ // algorithm. Password creation can be done in several iterations.
+ PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, saltValueBytes, hashAlgorithm, passwordIterations);
+
+ // Use the password to generate pseudo-random bytes for the encryption
+ // key. Specify the size of the key in bytes (instead of bits).
+ byte[] keyBytes = password.GetBytes(keySize / 8);
+
+ // Create uninitialized Rijndael encryption object.
+ RijndaelManaged symmetricKey = new RijndaelManaged();
+
+ // It is reasonable to set encryption mode to Cipher Block Chaining
+ // (CBC). Use default options for other symmetric key parameters.
+ symmetricKey.Mode = CipherMode.CBC;
+
+ // Generate encryptor from the existing key bytes and initialization
+ // vector. Key size will be defined based on the number of the key
+ // bytes.
+ ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);
+
+ // Define memory stream which will be used to hold encrypted data.
+ MemoryStream memoryStream = new MemoryStream();
+
+ // Define cryptographic stream (always use Write mode for encryption).
+ CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
+ // Start encrypting.
+ cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
+
+ // Finish encrypting.
+ cryptoStream.FlushFinalBlock();
+
+ // Convert our encrypted data from a memory stream into a byte array.
+ byte[] cipherTextBytes = memoryStream.ToArray();
+
+ // Close both streams.
+ memoryStream.Close();
+ cryptoStream.Close();
+
+ // Convert encrypted data into a base64-encoded string.
+ string cipherText = Convert.ToBase64String(cipherTextBytes);
+
+ // Return encrypted string.
+ return cipherText;
+ }
+
+ ///
+ /// Decrypts specified ciphertext using Rijndael symmetric key algorithm.
+ ///
+ ///
+ /// Base64-formatted ciphertext value.
+ ///
+ ///
+ /// Passphrase from which a pseudo-random password will be derived. The
+ /// derived password will be used to generate the encryption key.
+ /// Passphrase can be any string. In this example we assume that this
+ /// passphrase is an ASCII string.
+ ///
+ ///
+ /// Salt value used along with passphrase to generate password. Salt can
+ /// be any string. In this example we assume that salt is an ASCII string.
+ ///
+ ///
+ /// Hash algorithm used to generate password. Allowed values are: "MD5" and
+ /// "SHA1". SHA1 hashes are a bit slower, but more secure than MD5 hashes.
+ ///
+ ///
+ /// Number of iterations used to generate password. One or two iterations
+ /// should be enough.
+ ///
+ ///
+ /// Initialization vector (or IV). This value is required to encrypt the
+ /// first block of plaintext data. For RijndaelManaged class IV must be
+ /// exactly 16 ASCII characters long.
+ ///
+ ///
+ /// Size of encryption key in bits. Allowed values are: 128, 192, and 256.
+ /// Longer keys are more secure than shorter keys.
+ ///
+ ///
+ /// Decrypted string value.
+ ///
+ ///
+ /// Most of the logic in this function is similar to the Encrypt
+ /// logic. In order for decryption to work, all parameters of this function
+ /// - except cipherText value - must match the corresponding parameters of
+ /// the Encrypt function which was called to generate the
+ /// ciphertext.
+ ///
+ public static string Decrypt(string cipherText,
+ string passPhrase,
+ string saltValue,
+ string hashAlgorithm,
+ int passwordIterations,
+ string initVector,
+ int keySize,
+ bool doDecrypt) {
+ if (doDecrypt) {
+ // Convert strings defining encryption key characteristics into byte
+ // arrays.
+ byte[] initVectorBytes = Encoding.UTF8.GetBytes(initVector);
+ byte[] saltValueBytes = Encoding.UTF8.GetBytes(saltValue);
+
+ // Convert our ciphertext into a byte array.
+ byte[] cipherTextBytes = Convert.FromBase64String(cipherText);
+
+ // First, we must create a password, from which the key will be
+ // derived. This password will be generated from the specified
+ // passphrase and salt value. The password will be created using
+ // the specified hash algorithm. Password creation can be done in
+ // several iterations.
+ PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, saltValueBytes, hashAlgorithm, passwordIterations);
+
+ // Use the password to generate pseudo-random bytes for the encryption
+ // key. Specify the size of the key in bytes (instead of bits).
+ byte[] keyBytes = password.GetBytes(keySize / 8);
+
+ // Create uninitialized Rijndael encryption object.
+ RijndaelManaged symmetricKey = new RijndaelManaged();
+
+ // It is reasonable to set encryption mode to Cipher Block Chaining
+ // (CBC). Use default options for other symmetric key parameters.
+ symmetricKey.Mode = CipherMode.CBC;
+
+ // Generate decryptor from the existing key bytes and initialization
+ // vector. Key size will be defined based on the number of the key
+ // bytes.
+ ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes);
+
+ // Define memory stream which will be used to hold encrypted data.
+ MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
+
+ // Define cryptographic stream (always use Read mode for encryption).
+ CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
+
+ // Since at this point we don't know what the size of decrypted data
+ // will be, allocate the buffer long enough to hold ciphertext;
+ // plaintext is never longer than ciphertext.
+ byte[] plainTextBytes = new byte[cipherTextBytes.Length];
+
+ // Start decrypting.
+ int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
+
+ // Close both streams.
+ memoryStream.Close();
+ cryptoStream.Close();
+
+ // Convert decrypted data into a string.
+ // Let us assume that the original plaintext string was UTF8-encoded.
+ string plainText = Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
+
+ // Return decrypted string.
+ return plainText;
+ } else {
+ return "";
+ }
+ }
+ #endregion
+ #endregion
+
+ }
+}
diff --git a/Core/Helper/FileHelper.cs b/Core/Helper/FileHelper.cs
new file mode 100644
index 0000000..b3abf53
--- /dev/null
+++ b/Core/Helper/FileHelper.cs
@@ -0,0 +1,76 @@
+using System;
+using System.IO;
+using Core.Main;
+
+namespace Core.Helper {
+ public static class FileHelper {
+ public static void WriteTextToFile(string folderPath, string fileName, string text) {
+ FileHelper.WriteTextToFile(folderPath, fileName, text, Constants.confMinDate, Constants.confMinDate);
+ }
+
+ public static void WriteTextToFile(string folderPath, string fileName, string text, DateTime creationTime, DateTime lastWriteTime) {
+ if (!Directory.Exists(folderPath)) {
+ Directory.CreateDirectory(folderPath);
+ }
+
+ File.WriteAllText(folderPath + fileName, text);
+
+ if (creationTime != Constants.confMinDate) {
+ File.SetCreationTimeUtc(folderPath + fileName, creationTime);
+ }
+
+ if (lastWriteTime != Constants.confMinDate) {
+ File.SetLastWriteTimeUtc(folderPath + fileName, lastWriteTime);
+ }
+ }
+
+ public static void CreateBackup(string filePath, string backupFolder) {
+ FileHelper.CreateBackup(filePath, backupFolder, "");
+ }
+
+ public static void CreateBackup(string filePath, string backupFolder, string backupFileName) {
+ if (!Directory.Exists(backupFolder)) {
+ Directory.CreateDirectory(backupFolder);
+ }
+
+ FileInfo file = new FileInfo(filePath);
+
+ string backupFilePath = backupFolder + DateTime.Now.ToString("yyyy-MM-dd_HH.mm.ss") + "_" + file.Name;
+ if (!backupFileName.Equals("")) {
+ backupFilePath = backupFolder + backupFileName;
+ }
+
+ File.Copy(file.FullName, backupFilePath, true);
+ }
+
+ public static void CleanupFilesMinutes(string folderPath, int maxMinutes) {
+ if (!Directory.Exists(folderPath)) {
+ Directory.CreateDirectory(folderPath);
+ }
+
+ DirectoryInfo folder = new DirectoryInfo(folderPath);
+ foreach (FileInfo file in folder.GetFiles()) {
+ DateTime maxAge = DateTime.Now.AddMinutes(-maxMinutes);
+
+ if (file.LastWriteTime < maxAge) {
+ File.Delete(file.FullName);
+ }
+ }
+ }
+
+ public static void CleanupFiles(string folderPath, int maxHours) {
+ if (!Directory.Exists(folderPath)) {
+ Directory.CreateDirectory(folderPath);
+ }
+
+ DirectoryInfo folder = new DirectoryInfo(folderPath);
+ foreach (FileInfo file in folder.GetFiles()) {
+ DateTime maxAge = DateTime.Now.AddHours(-(maxHours + 1));
+
+ if (file.LastWriteTime < maxAge) {
+ File.Delete(file.FullName);
+ }
+ }
+ }
+ }
+}
diff --git a/Core/Helper/LogHelper.cs b/Core/Helper/LogHelper.cs
new file mode 100644
index 0000000..6750ef9
--- /dev/null
+++ b/Core/Helper/LogHelper.cs
@@ -0,0 +1,34 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using NLog.Extensions.Logging;
+
+namespace Core.Helper {
+ public class LogHelper {
+ private readonly ILogger log;
+
+ public LogHelper(ILogger logger) {
+ log = logger;
+ }
+
+ public void DoLogInfo(string message) {
+ if (log.IsEnabled(LogLevel.Information)) log.LogInformation(message);
+ }
+
+ public void DoLogWarn(string message) {
+ if (log.IsEnabled(LogLevel.Warning)) log.LogWarning(message);
+ }
+
+ public void DoLogError(string message) {
+ if (log.IsEnabled(LogLevel.Error)) log.LogError(message);
+ }
+
+ public void DoLogCritical(string message, System.Exception ex) {
+ if (log.IsEnabled(LogLevel.Critical)) log.LogCritical(ex, message);
+ }
+
+ public void DoLogDebug(string message) {
+ if (log.IsEnabled(LogLevel.Debug)) log.LogDebug(message);
+ }
+ }
+}
diff --git a/Core/Helper/ServiceHelper.cs b/Core/Helper/ServiceHelper.cs
new file mode 100644
index 0000000..3050547
--- /dev/null
+++ b/Core/Helper/ServiceHelper.cs
@@ -0,0 +1,33 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using NLog.Extensions.Logging;
+
+namespace Core.Helper {
+ public static class ServiceHelper {
+
+ public static IServiceProvider BuildLoggerService() {
+ return ServiceHelper.BuildLoggerService("");
+ }
+
+ public static IServiceProvider BuildLoggerService(string basePath) {
+ ServiceCollection services = new ServiceCollection();
+
+ services.AddTransient();
+
+ services.AddSingleton();
+ services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
+ services.AddLogging((builder) => builder.SetMinimumLevel(LogLevel.Trace));
+
+ ServiceProvider serviceProvider = services.BuildServiceProvider();
+
+ ILoggerFactory loggerFactory = serviceProvider.GetRequiredService();
+
+ //configure NLog
+ loggerFactory.AddNLog(new NLogProviderOptions { CaptureMessageTemplates = true, CaptureMessageProperties = true });
+ loggerFactory.ConfigureNLog(basePath + "nlog.config");
+
+ return serviceProvider;
+ }
+ }
+}
diff --git a/Core/Helper/SystemHelper.cs b/Core/Helper/SystemHelper.cs
new file mode 100644
index 0000000..b3c968e
--- /dev/null
+++ b/Core/Helper/SystemHelper.cs
@@ -0,0 +1,549 @@
+using System;
+using System.Text.RegularExpressions;
+using System.IO;
+using System.Collections;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using System.Linq;
+using System.Text;
+using System.Globalization;
+using Core.Main;
+
+namespace Core.Helper {
+
+ public class SystemHelper {
+ private static bool AllwaysGoodCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors policyErrors) {
+ return true;
+ }
+
+ ///
+ /// Checks, if a string is numeric.
+ ///
+ /// The string to check.
+ /// True, if the string is numeric.
+ public static bool IsNumeric(string s) {
+ try {
+ Int32.Parse(s);
+ } catch {
+ return false;
+ }
+ return true;
+ }
+
+ public static bool IsInteger(double d) {
+ return d % 1 == 0;
+ }
+
+ public static bool IsBoolean(string s) {
+ try {
+ Boolean.Parse(s);
+ } catch {
+ return false;
+ }
+ return true;
+ }
+
+ ///
+ /// Checks, if a string is a double value.
+ ///
+ /// The string to check.
+ /// True, if the string is a double value.
+ public static bool IsDouble(string s) {
+ try {
+ Double.Parse(s);
+ } catch {
+ return false;
+ }
+ return true;
+ }
+
+ public static bool IsDouble(string s, string culture) {
+ try {
+ Double.Parse(s, new CultureInfo(culture));
+ } catch {
+ return false;
+ }
+ return true;
+ }
+
+ ///
+ /// Checks, if a string is a DateTime value.
+ ///
+ /// The string to check.
+ /// True, if the string is a DateTime value.
+ public static bool IsDateTime(string s) {
+ try {
+ DateTime.Parse(s);
+ } catch {
+ return false;
+ }
+ return true;
+ }
+
+ ///
+ /// Konvertiert einen Text zu einem Integer-Wert mit Fehlerbehandlung.
+ ///
+ /// Zu konvertierender Text.
+ /// Der Vorgabewert für den Fall, dass keine gültige Zahl eingegeben wurde.
+ /// Den Text als Integer. Wenn die Konvertierung fehlschlägt, dann wird der Defaultwert zurückgegeben.
+ public static int TextToInteger(string text, int defaultValue) {
+ int result = defaultValue;
+ try {
+ string localText = text.Replace(".", "");
+ result = Convert.ToInt32(localText.Trim());
+ } catch { }
+
+ return result;
+ }
+
+ ///
+ /// Konvertiert einen Text zu einem Integer64-Wert mit Fehlerbehandlung.
+ ///
+ /// Zu konvertierender Text.
+ /// Der Vorgabewert für den Fall, dass keine gültige Zahl eingegeben wurde.
+ /// Den Text als Integer64. Wenn die Konvertierung fehlschlägt, dann wird der Defaultwert zurückgegeben.
+ public static Int64 TextToInteger64(string text, Int64 defaultValue) {
+ Int64 result = defaultValue;
+ try {
+ string localText = text.Replace(".", "");
+ result = Convert.ToInt64(localText.Trim());
+ } catch { }
+
+ return result;
+ }
+
+ public static double TextToDouble(string text, double defaultValue, string culture) {
+ double result = defaultValue;
+ try {
+ if (!string.IsNullOrEmpty(text)) {
+ double.TryParse(text, NumberStyles.Any, new System.Globalization.CultureInfo(culture), out result);
+ }
+ } catch { }
+
+ return result;
+ }
+
+ ///
+ /// Konvertiert einen Text zu einem DateTime-Wert mit Fehlerbehandlung.
+ ///
+ /// Zu konvertierender Text.
+ /// Der Vorgabewert für den Fall, dass keine gültige DateTime eingegeben wurde.
+ /// Den Text als DateTime. Wenn die Konvertierung fehlschlägt, dann wird der Defaultwert zurückgegeben.
+ public static DateTime TextToDateTime(string text, DateTime defaultValue) {
+ DateTime result = defaultValue;
+ try {
+ result = Convert.ToDateTime(text.Trim());
+ } catch { }
+
+ return result;
+ }
+
+ public static DateTime TextToDateTime(string text, DateTime defaultValue, string culture) {
+ DateTime result = defaultValue;
+ try {
+ result = Convert.ToDateTime(text.Trim(), new System.Globalization.CultureInfo(culture));
+ } catch { }
+
+ return result;
+ }
+
+ ///
+ /// Konvertiert einen Text zu einem Boolean-Wert mit Fehlerbehandlung.
+ ///
+ /// Zu konvertierender Text.
+ /// Der Vorgabewert für den Fall, dass keine gültige Boolean eingegeben wurde.
+ /// Den Text als Boolean. Wenn die Konvertierung fehlschlägt, dann wird der Defaultwert zurückgegeben.
+ public static bool TextToBoolean(string text, bool defaultValue) {
+ bool result = defaultValue;
+ try {
+ result = Convert.ToBoolean(text.Trim());
+ } catch {
+ try {
+ int intValue = Convert.ToInt32(text.Trim());
+ result = intValue == 0 ? false : true;
+ } catch { }
+ }
+
+ return result;
+ }
+
+ public static string SplitCamelCase(string s) {
+ string result = "";
+
+ string whiteList = "ABCDEFGHIJKLMNOPQRSTUVWXYZÄÜÖßabcdefghijklmnopqrstuvwxyzäüö0123456789_- ";
+
+ if (!string.IsNullOrEmpty(s)) {
+ for (int i = 0; i < s.Length; i++) {
+ if (char.IsUpper(s[i]) || char.IsNumber(s[i])) {
+ if (i > 0 && whiteList.Contains(s[i - 1].ToString())) {
+ if (char.IsUpper(s[i])) {
+ if (!char.IsUpper(s[i - 1]) && !char.IsNumber(s[i - 1])) result += " ";
+ } else if (char.IsNumber(s[i])) {
+ if (!char.IsNumber(s[i - 1])) result += " ";
+ }
+ }
+ }
+ result += s[i].ToString();
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Clears a string using a whitelist.
+ ///
+ /// Text to clear.
+ /// Allowed characters.
+ /// The cleared text.
+ public static string StripBadCode(string text, string allowedCharacters) {
+ StringBuilder sb = new StringBuilder();
+ if (text != null) {
+ for (int i = 0; i < text.Length; i++) {
+ if (allowedCharacters.Contains(text[i].ToString())) sb.Append(text[i]);
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ public static bool CheckForBadCode(string text, string allowedCharacters) {
+ bool result = false;
+ for (int i = 0; i < text.Length; i++) {
+ if (!allowedCharacters.Contains(text[i].ToString())) {
+ result = true;
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Schneidet einen Text nach x Zeichen ab
+ ///
+ /// Der Text, der gekürzt werden soll.
+ /// Die maximale Länge, auf die der Text gekürzt werden soll.
+ /// Der gekürzte Text.
+ public static string CutText(string text, int maxLength, bool addDots) {
+ string result = text;
+
+ if (result.Length > maxLength) {
+ result = result.Substring(0, maxLength);
+
+ if (addDots) result += "...";
+ }
+
+ return result;
+ }
+
+ ///
+ /// Ermittelt den Teilstring eines Zeitstring, der die Stunden darstellt.
+ ///
+ public static string GetHourFromString(string timeString) {
+ string result = "";
+
+ if (timeString.Contains(":")) {
+ string[] arrTime = timeString.Split(":".ToCharArray());
+ result = arrTime[0];
+ }
+
+ return result;
+ }
+
+ ///
+ /// Ermittelt den Teilstring eines Zeitstring, der die Minuten darstellt.
+ ///
+ public static string GetMinutesFromString(string timeString) {
+ string result = "";
+
+ if (timeString.Contains(":")) {
+ string[] arrTime = timeString.Split(":".ToCharArray());
+ result = arrTime[1];
+ }
+
+ return result;
+ }
+
+ public static List ConvertTokenStringToList(string tokenizedString, string separator) {
+ List result = new List();
+
+ if (!String.IsNullOrEmpty(tokenizedString) && !String.IsNullOrEmpty(separator)) {
+ string[] arrTokens = tokenizedString.Split(separator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
+ for (int i = 0; i < arrTokens.Length; i++) {
+ result.Add(arrTokens[i].Trim());
+ }
+ }
+
+ return result;
+ }
+
+ public static List ConvertTokenStringToListInt(string tokenizedString, string separator) {
+ List result = new List();
+
+ if (!String.IsNullOrEmpty(tokenizedString) && !String.IsNullOrEmpty(separator)) {
+ string[] arrTokens = tokenizedString.Split(separator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
+ for (int i = 0; i < arrTokens.Length; i++) {
+ result.Add(Convert.ToInt32(arrTokens[i]));
+ }
+ }
+
+ return result;
+ }
+
+ public static string ConvertListToTokenString(List tokenList, string separator, bool cropDoubleSeparators) {
+ string result = "";
+
+ if (tokenList.Count > 0) {
+ for (int i = 0; i < tokenList.Count; i++) {
+ result += tokenList[i].Trim() + separator;
+ }
+
+ if (cropDoubleSeparators)result = result.Replace(separator + separator, "");
+ }
+
+ return result;
+ }
+
+ public static string ConvertListToTokenString(List tokenList, string separator) {
+ string result = "";
+
+ if (tokenList.Count > 0) {
+ for (int i = 0; i < tokenList.Count; i++) {
+ result += tokenList[i].ToString() + separator;
+ }
+
+ result += separator;
+ result = result.Replace(separator + separator, "");
+ }
+
+ return result;
+ }
+
+ public static List