using System; using System.Collections.Generic; using System.IO; using Newtonsoft.Json; namespace SyncEngine.Configuration { public sealed class SyncOptions { public string AccessDbPath { get; set; } public string AccessDbPassword { get; set; } /// Empty or "current" = Application.CurrentUser for GRANT; Shell Runtime open uses Admin when empty. public string AccessDbUser { get; set; } /// Workgroup (/user) account password for Access Runtime Shell. Not the database encrypt password. public string AccessWorkgroupPassword { get; set; } public string SqlExpressConnectionString { get; set; } public string SqlExpressDatabase { get; set; } public string SqlExpressSchema { get; set; } public string MySqlHost { get; set; } public int MySqlPort { get; set; } public string MySqlUser { get; set; } public string MySqlPassword { get; set; } public string MySqlDatabase { get; set; } public string ShopSyncApiUrl { get; set; } public string ShopSyncApiKey { get; set; } public bool SyncAccessToSqlExpress { get; set; } public bool SyncSqlExpressToMySql { get; set; } public IReadOnlyList AccessSqlExcludeTables { get; set; } public int BatchSize { get; set; } public int MaxRetries { get; set; } public int CommandTimeoutSeconds { get; set; } public bool SoftDeleteEnabled { get; set; } public bool DryRun { get; set; } public string PlatformTarget { get; set; } public string ConfigDirectory { get; set; } public ProductSyncMap ProductSyncMap { get; set; } public static SyncOptions LoadFromEnvironment() { var excludeRaw = EnvSettings.Get("ACCESS_SQL_EXCLUDE_TABLES", ""); var configDir = Path.Combine(EnvSettings.ExeDirectory(), "config"); var mapPath = Path.Combine(configDir, "product-sync-map.json"); ProductSyncMap map = null; if (File.Exists(mapPath)) { map = JsonConvert.DeserializeObject(File.ReadAllText(mapPath)); } if (map == null) { map = new ProductSyncMap(); } var sqlCs = EnvSettings.Get("SQL_EXPRESS_CONNECTION_STRING", ""); var sqlDb = EnvSettings.Get("SQL_EXPRESS_DATABASE", "AnniesAngels"); return new SyncOptions { AccessDbPath = EnvSettings.ResolvePath(EnvSettings.Get("ACCESS_DB_PATH", "")), AccessDbPassword = EnvSettings.Get("ACCESS_DB_PASSWORD", ""), AccessDbUser = EnvSettings.Get("ACCESS_DB_USER", ""), AccessWorkgroupPassword = EnvSettings.Get("ACCESS_WORKGROUP_PASSWORD", ""), SqlExpressConnectionString = BuildSqlConnectionString(sqlCs, sqlDb), SqlExpressDatabase = sqlDb, SqlExpressSchema = EnvSettings.Get("SQL_EXPRESS_SCHEMA", "dbo"), MySqlHost = EnvSettings.Get("MYSQL_HOST", "127.0.0.1"), MySqlPort = EnvSettings.GetInt("MYSQL_PORT", 3306), MySqlUser = EnvSettings.Get("MYSQL_USER", ""), MySqlPassword = EnvSettings.Get("MYSQL_PASSWORD", ""), MySqlDatabase = EnvSettings.Get("MYSQL_DATABASE", "annies_angels_shop"), ShopSyncApiUrl = EnvSettings.Get("SHOP_SYNC_API_URL", ""), ShopSyncApiKey = EnvSettings.Get("SHOP_SYNC_API_KEY", ""), SyncAccessToSqlExpress = EnvSettings.GetBool("SYNC_ACCESS_TO_SQL_EXPRESS", true), SyncSqlExpressToMySql = EnvSettings.GetBool("SYNC_SQL_EXPRESS_TO_MYSQL", true), AccessSqlExcludeTables = TableExcludeFilter.Parse(excludeRaw), BatchSize = EnvSettings.GetInt("SYNC_BATCH_SIZE", 5000), MaxRetries = EnvSettings.GetInt("SYNC_MAX_RETRIES", 3), CommandTimeoutSeconds = EnvSettings.GetInt("SYNC_COMMAND_TIMEOUT_SECONDS", 120), SoftDeleteEnabled = EnvSettings.GetBool("SYNC_SOFT_DELETE_ENABLED", true), DryRun = EnvSettings.GetBool("SYNC_DRY_RUN", false), PlatformTarget = EnvSettings.Get("SYNC_PLATFORM_TARGET", "x64"), ConfigDirectory = configDir, ProductSyncMap = map }; } private static string BuildSqlConnectionString(string baseCs, string database) { if (string.IsNullOrWhiteSpace(baseCs)) { return string.Format("Server=.\\SQLEXPRESS;Database={0};Trusted_Connection=True;TrustServerCertificate=True", database); } if (baseCs.IndexOf("Database=", StringComparison.OrdinalIgnoreCase) >= 0 || baseCs.IndexOf("Initial Catalog=", StringComparison.OrdinalIgnoreCase) >= 0) { return baseCs; } var sep = baseCs.EndsWith(";", StringComparison.Ordinal) ? "" : ";"; return baseCs + sep + "Database=" + database; } public string MySqlConnectionString { get { // TlsVersion=Tls12: on SBS 2011 / Server 2008 R2, Schannel OS defaults omit TLS 1.2 // (DisabledByDefault). Explicit Tls12 keeps SSL working without a reboot. // Connection Timeout bounds TCP/TLS handshake so a hang fails within 30s. // UseAffectedRows=true: IODKU reports 1=insert, 2=update, 0=unchanged (classic MySQL counts). return string.Format( "Server={0};Port={1};User ID={2};Password={3};Database={4};SslMode=Preferred;TlsVersion=Tls12;Connection Timeout=30;Allow User Variables=True;Use Affected Rows=True", MySqlHost, MySqlPort, MySqlUser, MySqlPassword, MySqlDatabase); } } public bool HasShopSyncApiFallback => !string.IsNullOrWhiteSpace(ShopSyncApiUrl) && !string.IsNullOrWhiteSpace(ShopSyncApiKey); } public sealed class ProductSyncMap { [JsonProperty("barcodeColumn")] public string BarcodeColumn { get; set; } = "strCode"; [JsonProperty("imageUrl1Column")] public string ImageUrl1Column { get; set; } = "strImagePath"; [JsonProperty("productTable")] public string ProductTable { get; set; } = "tblProduct"; [JsonProperty("productIdColumn")] public string ProductIdColumn { get; set; } = "ProductID"; } }