diff --git a/Builder/IISMainHandler/build.txt b/Builder/IISMainHandler/build.txt index 47c57f6..69341ae 100644 --- a/Builder/IISMainHandler/build.txt +++ b/Builder/IISMainHandler/build.txt @@ -1 +1 @@ -1981 \ No newline at end of file +1985 \ No newline at end of file diff --git a/FLocal.Patcher.Common/PatcherConfiguration.cs b/FLocal.Patcher.Common/PatcherConfiguration.cs index 5e5dd7c..b35d07c 100644 --- a/FLocal.Patcher.Common/PatcherConfiguration.cs +++ b/FLocal.Patcher.Common/PatcherConfiguration.cs @@ -13,22 +13,30 @@ namespace FLocal.Patcher.Common { private readonly string _DbDriverName; public string DbDriverName { - get { throw new NotImplementedException(); } + get { + return this._DbDriverName; + } } private readonly string _GuestConnectionString; public string GuestConnectionString { - get { throw new NotImplementedException(); } + get { + return this._GuestConnectionString; + } } private readonly string _PatchesTable; public string PatchesTable { - get { throw new NotImplementedException(); } + get { + return this._PatchesTable; + } } private readonly string _EnvironmentName; public string EnvironmentName { - get { throw new NotImplementedException(); } + get { + return this._EnvironmentName; + } } public IEnumerable getPatchesList() { @@ -40,10 +48,10 @@ namespace FLocal.Patcher.Common { } protected PatcherConfiguration(NameValueCollection data) : base() { - this._DbDriverName = data["Patcher.DbDriver"]; - this._EnvironmentName = data["Patcher.EnvironmentName"]; - this._GuestConnectionString = data["ConnectionString"]; - this._PatchesTable = data["Patcher.PatchesTable"]; + this._DbDriverName = data["Patcher.DbDriver"].ToString(); + this._EnvironmentName = data["Patcher.EnvironmentName"].ToString(); + this._GuestConnectionString = data["ConnectionString"].ToString(); + this._PatchesTable = data["Patcher.PatchesTable"].ToString(); } public static void Init(NameValueCollection data) { diff --git a/FLocal.Patcher.IISHandler/MainHandler.cs b/FLocal.Patcher.IISHandler/MainHandler.cs index 38ae00b..638daf0 100644 --- a/FLocal.Patcher.IISHandler/MainHandler.cs +++ b/FLocal.Patcher.IISHandler/MainHandler.cs @@ -15,7 +15,7 @@ namespace FLocal.Patcher.IISHandler { } protected override string GetAdminConnectionString(HttpContext context) { - throw new NotImplementedException(); + return System.Configuration.ConfigurationManager.AppSettings["Patcher.AdminConnectionString"].Replace("{password}", context.Request.Form["data"]); } } diff --git a/Patcher.Web/CheckParams.cs b/Patcher.Web/CheckParams.cs index 5086e24..2fa0ba7 100644 --- a/Patcher.Web/CheckParams.cs +++ b/Patcher.Web/CheckParams.cs @@ -7,7 +7,7 @@ using Patcher.Data.Patch; namespace Patcher.Web { class CheckParams : ICheckParams { - private readonly IPatcherConfiguration configuration; + protected readonly IPatcherConfiguration configuration; public CheckParams(IPatcherConfiguration configuration) { this.configuration = configuration; @@ -19,7 +19,7 @@ namespace Patcher.Web { } } - public string ConnectionString { + public virtual string ConnectionString { get { return this.configuration.GuestConnectionString; } diff --git a/Patcher.Web/InteractiveResponseStream.cs b/Patcher.Web/InteractiveResponseStream.cs new file mode 100644 index 0000000..2b11521 --- /dev/null +++ b/Patcher.Web/InteractiveResponseStream.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Web.Core; +using System.Web; + +namespace Patcher.Web { + class InteractiveResponseStream : IInteractiveConsole { + + private readonly HttpContext context; + + public void Report(string message) { + this.context.Response.Output.WriteLine(message); + } + + public void Report(string format, params object[] data) { + this.context.Response.Output.WriteLine(format, data); + } + + public bool IsInteractive { + get { + return false; + } + } + + public bool Ask(string question) { + throw new NotImplementedException(); + } + + public void WaitForUserAction() { + throw new NotImplementedException(); + } + + public InteractiveResponseStream(HttpContext context) { + this.context = context; + } + + } +} diff --git a/Patcher.Web/MainHandler.cs b/Patcher.Web/MainHandler.cs index a2218b7..80d2811 100644 --- a/Patcher.Web/MainHandler.cs +++ b/Patcher.Web/MainHandler.cs @@ -18,16 +18,35 @@ namespace Patcher.Web { } private void Install(HttpContext context) { - var writer = context.Response.Output; - writer.WriteLine("

Installing...

"); + context.Response.ContentType = "text/plain"; + context.Response.Output.WriteLine("Installing..."); System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2)); - this.patcherInfo.AreNewPatchesInstalled = true; - writer.WriteLine("

Installed

"); + var updater = new Updater(new UpdateParams(this.patcherInfo.configuration, this.GetAdminConnectionString(context)), new InteractiveResponseStream(context)); + int resultCode = updater.ApplyAll(); + if(resultCode == 0) { + this.patcherInfo.AreNewPatchesInstalled = true; + context.Response.Output.WriteLine("Installed"); + } else { + context.Response.Output.WriteLine("Failed to install: error code {0}", resultCode); + } } private void ShowInfo(HttpContext context) { var writer = context.Response.Output; - writer.WriteLine("
"); + var checker = new Checker(new CheckParams(this.patcherInfo.configuration)); + int totalPatches = 0; + foreach(var patchId in checker.GetPatchesToInstall()) { + writer.WriteLine("

{0}: \"{1}\"

", patchId.version, patchId.name); + totalPatches++; + } + writer.WriteLine("

Total patches: {0}", totalPatches); + if(totalPatches > 0) { + writer.WriteLine("

"); + writer.WriteLine("
"); + writer.WriteLine(""); + writer.WriteLine(""); + writer.WriteLine("
"); + } } public void ProcessRequest(HttpContext context) { diff --git a/Patcher.Web/Patcher.Web.csproj b/Patcher.Web/Patcher.Web.csproj index a92adf9..6a28c65 100644 --- a/Patcher.Web/Patcher.Web.csproj +++ b/Patcher.Web/Patcher.Web.csproj @@ -47,10 +47,12 @@ + + diff --git a/Patcher.Web/UpdateParams.cs b/Patcher.Web/UpdateParams.cs new file mode 100644 index 0000000..7758828 --- /dev/null +++ b/Patcher.Web/UpdateParams.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using Patcher.Data.Patch; + +namespace Patcher.Web { + class UpdateParams : CheckParams, IUpdateParams { + + private readonly string AdminConnectionString; + + public UpdateParams(IPatcherConfiguration configuration, string AdminConnectionString) : base(configuration) { + this.AdminConnectionString = AdminConnectionString; + } + + public override string ConnectionString { + get { + return this.AdminConnectionString; + } + } + + public string EnvironmentName { + get { + return configuration.EnvironmentName; + } + } + + public Stream loadPatch(PatchId patchId) { + return configuration.loadPatch(patchId); + } + + } +} diff --git a/Patcher/Checker.cs b/Patcher/Checker.cs index c59c799..0d38f23 100644 --- a/Patcher/Checker.cs +++ b/Patcher/Checker.cs @@ -25,30 +25,30 @@ namespace Patcher { from patchId in this.checkParams.getPatchesList() orderby patchId ascending select patchId, - from row in transaction.ExecuteReader( - string.Format( - "select {1}, {2} from {0} where {3} = {4}", - transaction.EscapeName(this.checkParams.PatchesTable), - transaction.EscapeName("VERSION"), - transaction.EscapeName("NAME"), - transaction.EscapeName("STATUS"), - transaction.MarkParam("pstatus") - ), - new Dictionary { - { "pstatus", STATUS_INSTALLED }, - } - ) - let patch = new PatchId(int.Parse(row["VERSION"]), row["NAME"]) - orderby patch ascending - select patch + ( + from row in transaction.ExecuteReader( + string.Format( + "select {1}, {2} from {0} where {3} = {4}", + transaction.EscapeName(this.checkParams.PatchesTable), + transaction.EscapeName("VERSION"), + transaction.EscapeName("NAME"), + transaction.EscapeName("STATUS"), + transaction.MarkParam("pstatus") + ), + new Dictionary { + { "pstatus", STATUS_INSTALLED }, + } + ) + let patch = new PatchId(int.Parse(row["VERSION"]), row["NAME"]) + orderby patch ascending + select patch + ).ToList() ); } } public bool IsNeedsPatching() { - System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2)); - return true; - //return this.GetPatchesToInstall().Any(); + return this.GetPatchesToInstall().Any(); } } diff --git a/Patcher/DB/DBTraitsFactory.cs b/Patcher/DB/DBTraitsFactory.cs index 58d951b..318e6a5 100644 --- a/Patcher/DB/DBTraitsFactory.cs +++ b/Patcher/DB/DBTraitsFactory.cs @@ -12,6 +12,8 @@ namespace Patcher.DB { return OracleDBTraits.instance; case "oracle-faketransactional": return OracleFakeTransactionalDBTraits.instance; + case "postgres": + return PostgresDBTraits.instance; default: throw new NotImplementedException(); } diff --git a/Patcher/DB/PostgresDBTraits.cs b/Patcher/DB/PostgresDBTraits.cs new file mode 100644 index 0000000..e80e176 --- /dev/null +++ b/Patcher/DB/PostgresDBTraits.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Npgsql; + +namespace Patcher.DB { + + class PostgresDBTraits : IDBTraits { + + public static readonly IDBTraits instance = new PostgresDBTraits(); + + protected PostgresDBTraits() { + } + + private static readonly Regex ALPHANUMERIC = new Regex("^[a-zA-Z]\\w*$", RegexOptions.Compiled | RegexOptions.Singleline); + + private static string _EscapeName(string name) { + if(!ALPHANUMERIC.IsMatch(name)) throw new ApplicationException("Name should contain only alphanumeric characters"); + return string.Format("\"{0}\"", name); + } + + string IDBTraits.EscapeName(string name) { + return _EscapeName(name); + } + + DbConnection IDBTraits.CreateConnection(string connectionString) { + NpgsqlConnection connection = new NpgsqlConnection(connectionString); + connection.Open(); + return connection; + } + + string IDBTraits.MarkParam(string paramName) { + if(!ALPHANUMERIC.IsMatch(paramName)) throw new ApplicationException("Name should contain only alphanumeric characters"); + return ":" + paramName; + } + + string IDBTraits.ParamName(string paramName) { + if(!ALPHANUMERIC.IsMatch(paramName)) throw new ApplicationException("Name should contain only alphanumeric characters"); + return paramName; + } + + private static void AddParam(DbCommand command, string name, DbType type, object value) { + var param = command.CreateParameter(); + param.ParameterName = name; + param.DbType = type; + param.Value = value; + command.Parameters.Add(param); + } + + private static bool ParseBoolString(string value) { + switch(value.ToLower()) { + case "n": + return false; + case "y": + return true; + default: + throw new ApplicationException(string.Format("Unknown value {0}", value)); + } + } + + private static T CastResult(object value) where T : class { + if(DBNull.Value.Equals(value)) { + return null; + } else { + return (T)value; + } + } + + private static T? CastScalarResult(object value) where T : struct { + if(DBNull.Value.Equals(value)) { + return null; + } else { + return (T)value; + } + } + + private static readonly SQLQueryManager _SQLQueryManager = new SQLQueryManager(_EscapeName); + + StoredProcedureBody IDBTraits.GetStoredProcedureBody(Func commandCreator, StoredProcedureReference procedure) { + throw new NotImplementedException(); + } + + private static string FormatStoredProcedureHeader(StoredProcedureReference procedure) { + throw new NotImplementedException(); + } + + void IDBTraits.ReplaceStoredProcedureBody(Func commandCreator, StoredProcedureReference procedure, StoredProcedureBody newBody) { + throw new NotImplementedException(); + } + + void IDBTraits.RemoveStoredProcedure(Func commandCreator, StoredProcedureReference procedure) { + throw new NotImplementedException(); + } + + void IDBTraits.CreateView(Func commandCreator, string viewName, string body) { + throw new NotImplementedException(); + } + + void IDBTraits.RemoveView(Func commandCreator, string viewName) { + throw new NotImplementedException(); + } + + void IDBTraits.CreateStoredProcedure(Func commandCreator, StoredProcedureReference procedure, StoredProcedureBody body) { + throw new NotImplementedException(); + } + + string IDBTraits.GetViewBody(Func commandCreator, string name) { + throw new NotImplementedException(); + } + + ColumnOptions IDBTraits.GetColumnOptions(Func commandCreator, ColumnReference column) { + throw new NotImplementedException(); + } + + void IDBTraits.RemoveColumn(Func commandCreator, ColumnReference column) { + using(DbCommand command = commandCreator()) { + command.CommandText = _SQLQueryManager.RemoveColumn(column); + command.ExecuteNonQuery(); + } + } + + void IDBTraits.CreateColumn(Func commandCreator, ColumnDescription description) { + using(DbCommand command = commandCreator()) { + command.CommandText = _SQLQueryManager.CreateColumn(description); + command.ExecuteNonQuery(); + } + } + + void IDBTraits.ModifyColumn(Func commandCreator, ColumnDescription description) { + using(DbCommand command = commandCreator()) { + command.CommandText = _SQLQueryManager.ModifyColumnPostgresStyle(description); + Console.WriteLine(); + Console.WriteLine(command.CommandText); + command.ExecuteNonQuery(); + } + } + + private static string GetStringRepresentation(ForeignKeyConstraint.ReferentialAction action) { + switch(action) { + case ForeignKeyConstraint.ReferentialAction.NoAction: + return "NO ACTION"; + case ForeignKeyConstraint.ReferentialAction.Cascade: + return "CASCADE"; + case ForeignKeyConstraint.ReferentialAction.SetNull: + return "SET NULL"; + case ForeignKeyConstraint.ReferentialAction.SetDefault: + return "SET DEFAULT"; + default: + throw new ApplicationException("Unknown referential action"); + } + } + + private void CheckConstraint(Func commandCreator, ForeignKeyConstraint constraint) { + throw new NotImplementedException(); + } + + private void CheckConstraint(Func commandCreator, UniqueConstraint constraint) { + throw new NotImplementedException(); + } + + private void CheckConstraint(Func commandCreator, CheckConstraint constraint) { + throw new NotImplementedException(); + } + + private void CheckConstraint(Func commandCreator, AbstractConstraint constraint) { + constraint.Accept(fkc => CheckConstraint(commandCreator, fkc), uc => CheckConstraint(commandCreator, uc), cc => CheckConstraint(commandCreator, cc)); + } + + public void RemoveConstraint(Func commandCreator, AbstractConstraint constraint) { + CheckConstraint(commandCreator, constraint); + using(DbCommand command = commandCreator()) { + command.CommandText = _SQLQueryManager.DropConstraint(constraint); + Console.WriteLine(); + Console.WriteLine(command.CommandText); + command.ExecuteNonQuery(); + } + } + + public void CreateConstraint(Func commandCreator, AbstractConstraint constraint) { + using(DbCommand command = commandCreator()) { + command.CommandText = _SQLQueryManager.CreateConstraint(constraint); + Console.WriteLine(); + Console.WriteLine(command.CommandText); + command.ExecuteNonQuery(); + } + } + + public void CreateTable(Func commandCreator, TableDescription table) { + using(DbCommand command = commandCreator()) { + command.CommandText = _SQLQueryManager.CreateTable(table); + Console.WriteLine(); + Console.WriteLine(command.CommandText); + command.ExecuteNonQuery(); + } + } + + private void CheckTable(Func commandCreator, TableDescription table) { + throw new NotImplementedException(); + } + + void IDBTraits.RemoveTable(Func commandCreator, TableDescription table) { + this.CheckTable(commandCreator, table); + using(DbCommand command = commandCreator()) { + command.CommandText = _SQLQueryManager.DropTable(table.table); + Console.WriteLine(); + Console.WriteLine(command.CommandText); + command.ExecuteNonQuery(); + } + } + + bool IDBTraits.IsDDLTransactional { + get { return true; } + } + + } +} diff --git a/Patcher/DB/SQLQueryManager.cs b/Patcher/DB/SQLQueryManager.cs index 27de17f..91f9218 100644 --- a/Patcher/DB/SQLQueryManager.cs +++ b/Patcher/DB/SQLQueryManager.cs @@ -253,6 +253,10 @@ namespace Patcher.DB ); } + private string _ModifyColumnDefinitionPostgresStyle(ColumnDescription description) { + throw new NotImplementedException(); + } + private string _TableElementList(TableDescription table) { return string.Format( "({0})", @@ -292,12 +296,16 @@ namespace Patcher.DB { return _AlterTableStatement(column.tableName, _DropColumnDefinition(column)); } - + public string ModifyColumnOracleStyle(ColumnDescription description) { return _AlterTableStatement(description.column.tableName, _ModifyColumnDefinitionOracleStyle(description)); } - + + public string ModifyColumnPostgresStyle(ColumnDescription description) { + return _AlterTableStatement(description.column.tableName, _ModifyColumnDefinitionPostgresStyle(description)); + } + public string CreateConstraint(AbstractConstraint constraint) { return _AlterTableStatement(constraint.table, _AddTableConstraintDefinition(constraint)); diff --git a/Patcher/Patcher.csproj b/Patcher/Patcher.csproj index 114e88b..e90bf70 100644 --- a/Patcher/Patcher.csproj +++ b/Patcher/Patcher.csproj @@ -31,6 +31,10 @@ 4 + + False + ..\ThirdParty\Npgsql\Npgsql.dll + 3.5 @@ -80,6 +84,7 @@ +