diff --git a/Builder/IISMainHandler/build.txt b/Builder/IISMainHandler/build.txt index 69341ae..c0d6611 100644 --- a/Builder/IISMainHandler/build.txt +++ b/Builder/IISMainHandler/build.txt @@ -1 +1 @@ -1985 \ No newline at end of file +1996 \ No newline at end of file diff --git a/FLocal.IISHandler/Initializer.cs b/FLocal.IISHandler/Initializer.cs index ad78d61..57ac9cc 100644 --- a/FLocal.IISHandler/Initializer.cs +++ b/FLocal.IISHandler/Initializer.cs @@ -17,6 +17,7 @@ namespace FLocal.IISHandler { public static readonly Initializer instance = new Initializer(); private bool isInitialized; + private bool isCached; private readonly object locker = new object(); @@ -25,6 +26,7 @@ namespace FLocal.IISHandler { /// private Initializer() { this.isInitialized = false; + this.isCached = false; } public void Initialize() { @@ -36,21 +38,33 @@ namespace FLocal.IISHandler { } } } + + if(!this.isCached) { + lock(this.locker) { + if(!this.isCached) { + this.DoCache(); + this.isCached = true; + } + } + } } private void DoInitialize() { Config.Init(ConfigurationManager.AppSettings); PatcherConfiguration.Init(ConfigurationManager.AppSettings); + } - string dir = FLocal.Common.Config.instance.dataDir + "Logs\\"; - using(StreamWriter writer = new StreamWriter(dir + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + ".INITIALIZE.txt")) { - writer.WriteLine("###INITIALIZE###"); - foreach(var cacher in this.cachers) { - System.Threading.ThreadPool.QueueUserWorkItem(this.GetCacheWrapper(cacher)); - writer.WriteLine("Pending " + cacher.Key); + private void DoCache() { + if(!PatcherInfo.instance.IsNeedsPatching) { + string dir = FLocal.Common.Config.instance.dataDir + "Logs\\"; + using(StreamWriter writer = new StreamWriter(dir + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + ".INITIALIZE.txt")) { + writer.WriteLine("###INITIALIZE###"); + foreach(var cacher in this.cachers) { + System.Threading.ThreadPool.QueueUserWorkItem(this.GetCacheWrapper(cacher)); + writer.WriteLine("Pending " + cacher.Key); + } } } - } private IEnumerable> cachers { diff --git a/FLocal.Patcher.IISHandler/MainHandler.cs b/FLocal.Patcher.IISHandler/MainHandler.cs index 638daf0..7e9df2f 100644 --- a/FLocal.Patcher.IISHandler/MainHandler.cs +++ b/FLocal.Patcher.IISHandler/MainHandler.cs @@ -15,7 +15,8 @@ namespace FLocal.Patcher.IISHandler { } protected override string GetAdminConnectionString(HttpContext context) { - return System.Configuration.ConfigurationManager.AppSettings["Patcher.AdminConnectionString"].Replace("{password}", context.Request.Form["data"]); + string[] parts = context.Request.Form["data"].Split(';'); + return System.Configuration.ConfigurationManager.AppSettings["Patcher.AdminConnectionString"].Replace("{username}", parts[0]).Replace("{password}", parts[1]); } } diff --git a/Patcher.Web/MainHandler.cs b/Patcher.Web/MainHandler.cs index 80d2811..2904a03 100644 --- a/Patcher.Web/MainHandler.cs +++ b/Patcher.Web/MainHandler.cs @@ -3,10 +3,14 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Web; +using System.IO; namespace Patcher.Web { abstract public class MainHandler : IHttpHandler { + private const string ACTION_INSTALLALL = "InstallAll"; + private const string ACTION_ROLLBACKLATEST = "RollbackLatest"; + abstract protected PatcherInfo patcherInfo { get; } @@ -20,38 +24,81 @@ namespace Patcher.Web { private void Install(HttpContext context) { context.Response.ContentType = "text/plain"; context.Response.Output.WriteLine("Installing..."); - System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2)); 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; + this.patcherInfo.PatchesInstalled(); context.Response.Output.WriteLine("Installed"); } else { context.Response.Output.WriteLine("Failed to install: error code {0}", resultCode); } } + private void RollbackLatest(HttpContext context) { + context.Response.ContentType = "text/plain"; + context.Response.Output.WriteLine("Uninstalling..."); + var updater = new Updater(new UpdateParams(this.patcherInfo.configuration, this.GetAdminConnectionString(context)), new InteractiveResponseStream(context)); + this.patcherInfo.DisallowMainHandler(); + int resultCode = updater.RollbackLastPatch(); + if(resultCode == 0) { + context.Response.Output.WriteLine("Uninstalled"); + } else { + context.Response.Output.WriteLine("Failed to uninstall: error code {0}", resultCode); + } + } + + private void WriteForm(TextWriter writer, string method, string methodDescription) { + writer.WriteLine("
"); + writer.WriteLine("
"); + writer.WriteLine("", method); + writer.WriteLine("", methodDescription); + writer.WriteLine("
"); + } + private void ShowInfo(HttpContext context) { var writer = context.Response.Output; 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("

Patches to install

"); + int totalPatches = 0; + + writer.WriteLine("
    "); + foreach(var patchId in checker.GetPatchesToInstall()) { + writer.WriteLine("
  1. {0}: \"{1}\"
  2. ", patchId.version, patchId.name); + totalPatches++; + } + writer.WriteLine("
"); + + writer.WriteLine("

Total patches: {0}

", totalPatches); + if(totalPatches > 0) { + WriteForm(writer, ACTION_INSTALLALL, "Install all patches"); + } } - writer.WriteLine("

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

"); - writer.WriteLine("
"); - writer.WriteLine(""); - writer.WriteLine(""); - writer.WriteLine("
"); + + { + writer.WriteLine("

Installed patches

"); + int totalPatches = 0; + + writer.WriteLine("
    "); + foreach(var patchId in checker.GetInstalledPatches()) { + writer.WriteLine("
  1. {0}: \"{1}\"
  2. ", patchId.version, patchId.name); + totalPatches++; + } + writer.WriteLine("
"); + + writer.WriteLine("

Total patches: {0}

", totalPatches); + if(totalPatches > 0) { + WriteForm(writer, ACTION_ROLLBACKLATEST, "Uninstall latest patch"); + } } } public void ProcessRequest(HttpContext context) { - if(context.Request.Form["install"] == "install") { + if(context.Request.Form[ACTION_INSTALLALL] == ACTION_INSTALLALL) { this.Install(context); + } else if(context.Request.Form[ACTION_ROLLBACKLATEST] == ACTION_ROLLBACKLATEST) { + this.RollbackLatest(context); } else { this.ShowInfo(context); } diff --git a/Patcher.Web/PatcherInfo.cs b/Patcher.Web/PatcherInfo.cs index 3fdd4b6..a142f6e 100644 --- a/Patcher.Web/PatcherInfo.cs +++ b/Patcher.Web/PatcherInfo.cs @@ -8,22 +8,33 @@ namespace Patcher.Web { internal readonly IPatcherConfiguration configuration; - public readonly bool IsContainsNewPatches; + internal readonly bool IsContainsNewPatches; - public bool AreNewPatchesInstalled { + internal bool AreNewPatchesInstalled { get; - internal set; + private set; } + private bool IsMainHandlerDisallowed; + public bool IsNeedsPatching { get { - return this.IsContainsNewPatches && !this.AreNewPatchesInstalled; + return (this.IsContainsNewPatches && !this.AreNewPatchesInstalled) || this.IsMainHandlerDisallowed; } } + internal void PatchesInstalled() { + this.AreNewPatchesInstalled = true; + } + + internal void DisallowMainHandler() { + this.IsMainHandlerDisallowed = true; + } + protected PatcherInfo(IPatcherConfiguration configuration) { this.configuration = configuration; this.IsContainsNewPatches = (new Checker(new CheckParams(configuration))).IsNeedsPatching(); + this.IsMainHandlerDisallowed = false; } } diff --git a/Patcher/Checker.cs b/Patcher/Checker.cs index 0d38f23..834b6f8 100644 --- a/Patcher/Checker.cs +++ b/Patcher/Checker.cs @@ -25,28 +25,34 @@ 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 - ).ToList() + this.GetInstalledPatches() ); } } + public IEnumerable GetInstalledPatches() { + using(Transaction transaction = TransactionFactory.Create(this.checkParams.DbDriverName, this.checkParams.ConnectionString)) { + return ( + 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() { return this.GetPatchesToInstall().Any(); } diff --git a/Patcher/Context.cs b/Patcher/Context.cs index b0d413d..8e4b50a 100644 --- a/Patcher/Context.cs +++ b/Patcher/Context.cs @@ -49,4 +49,26 @@ namespace Patcher } } + + class Logger : ILogger { + + public static readonly ILogger instance = new Logger(); + + private readonly StreamWriter writer; + + private readonly object locker = new object(); + + private Logger() { + this.writer = new StreamWriter("C:\\Program Files\\FLocal\\main\\debug\\data\\Logs\\" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + ".patcher.txt"); + } + + void ILogger.Log(string message) { + lock(this.locker) { + this.writer.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffffff") + ": " + message); + this.writer.Flush(); + } + } + + } + } diff --git a/Patcher/DB/PostgresDBTraits.cs b/Patcher/DB/PostgresDBTraits.cs index e80e176..60a0e91 100644 --- a/Patcher/DB/PostgresDBTraits.cs +++ b/Patcher/DB/PostgresDBTraits.cs @@ -13,6 +13,16 @@ namespace Patcher.DB { class PostgresDBTraits : IDBTraits { + private static DbDataReader ExecuteReader(DbCommand command) { + Logger.instance.Log(command.CommandText); + return command.ExecuteReader(); + } + + private static int ExecuteNonQuery(DbCommand command) { + Logger.instance.Log(command.CommandText); + return command.ExecuteNonQuery(); + } + public static readonly IDBTraits instance = new PostgresDBTraits(); protected PostgresDBTraits() { @@ -64,6 +74,15 @@ namespace Patcher.DB { } } + private static string ParseTypeString(string type) { + switch(type.ToLower()) { + case "int4": + return "integer"; + default: + return type; + } + } + private static T CastResult(object value) where T : class { if(DBNull.Value.Equals(value)) { return null; @@ -115,29 +134,42 @@ namespace Patcher.DB { } ColumnOptions IDBTraits.GetColumnOptions(Func commandCreator, ColumnReference column) { - throw new NotImplementedException(); + using(DbCommand command = commandCreator()) { + command.CommandText = "SELECT a.attnum, a.attname AS field, t.typname AS type, a.attlen AS length, a.atttypmod AS lengthvar, a.attnotnull AS notnull, d.adsrc AS defaultvalue FROM pg_attribute a JOIN pg_class c ON a.attrelid = c.oid JOIN pg_type t ON a.atttypid = t.oid LEFT JOIN pg_attrdef d ON c.oid = d.adrelid AND a.attnum = d.adnum WHERE a.attnum > 0 AND c.relname = :ptable and a.attname = :pcolumn"; + AddParam(command, "ptable", DbType.String, column.tableName); + AddParam(command, "pcolumn", DbType.String, column.columnName); + using(DbDataReader reader = ExecuteReader(command)) { + if(!reader.Read()) { + throw new ApplicationException("Column not found"); + } + + return new ColumnOptions( + ParseTypeString(reader.GetString(reader.GetOrdinal("type"))), + reader.GetString(reader.GetOrdinal("defaultvalue")), + reader.GetBoolean(reader.GetOrdinal("notnull")) + ); + } + } } void IDBTraits.RemoveColumn(Func commandCreator, ColumnReference column) { using(DbCommand command = commandCreator()) { command.CommandText = _SQLQueryManager.RemoveColumn(column); - command.ExecuteNonQuery(); + ExecuteNonQuery(command); } } void IDBTraits.CreateColumn(Func commandCreator, ColumnDescription description) { using(DbCommand command = commandCreator()) { command.CommandText = _SQLQueryManager.CreateColumn(description); - command.ExecuteNonQuery(); + ExecuteNonQuery(command); } } void IDBTraits.ModifyColumn(Func commandCreator, ColumnDescription description) { using(DbCommand command = commandCreator()) { command.CommandText = _SQLQueryManager.ModifyColumnPostgresStyle(description); - Console.WriteLine(); - Console.WriteLine(command.CommandText); - command.ExecuteNonQuery(); + ExecuteNonQuery(command); } } @@ -157,14 +189,53 @@ namespace Patcher.DB { } private void CheckConstraint(Func commandCreator, ForeignKeyConstraint constraint) { + using(DbCommand command = commandCreator()) { + command.CommandText = string.Format("\\d {0}", _EscapeName(constraint.name)); + using(var reader = ExecuteReader(command)) { + int row = 0; + while(reader.Read()) { + Logger.instance.Log("Row #" + row); + for(int j=0; j commandCreator, UniqueConstraint constraint) { + using(DbCommand command = commandCreator()) { + command.CommandText = string.Format("\\d {0}", _EscapeName(constraint.name)); + using(var reader = ExecuteReader(command)) { + int row = 0; + while(reader.Read()) { + Logger.instance.Log("Row #" + row); + for(int j=0; j commandCreator, CheckConstraint constraint) { + using(DbCommand command = commandCreator()) { + command.CommandText = string.Format("\\d {0}", _EscapeName(constraint.name)); + using(var reader = ExecuteReader(command)) { + int row = 0; + while(reader.Read()) { + Logger.instance.Log("Row #" + row); + for(int j=0; j commandCreator, AbstractConstraint constraint) { using(DbCommand command = commandCreator()) { command.CommandText = _SQLQueryManager.CreateConstraint(constraint); - Console.WriteLine(); - Console.WriteLine(command.CommandText); - command.ExecuteNonQuery(); + ExecuteNonQuery(command); } } public void CreateTable(Func commandCreator, TableDescription table) { using(DbCommand command = commandCreator()) { command.CommandText = _SQLQueryManager.CreateTable(table); - Console.WriteLine(); - Console.WriteLine(command.CommandText); - command.ExecuteNonQuery(); + ExecuteNonQuery(command); } } private void CheckTable(Func commandCreator, TableDescription table) { - throw new NotImplementedException(); + HashSet columns = new HashSet(from column in table.columns select column.column.columnName); + columns.Add(table.primaryKey.column.columnName); + + using(DbCommand command = commandCreator()) + { + command.CommandText = "SELECT attname FROM pg_attribute, pg_class WHERE pg_class.oid = attrelid AND attnum>0 AND relname = ':ptable'"; + AddParam(command, "ptable", DbType.String, table.table); + + using(var reader = ExecuteReader(command)) + { + HashSet dbColumns = new HashSet(); + while(reader.Read()) + { + dbColumns.Add(reader.GetValue("attname").ToString()); + } + + if(!dbColumns.IsSubsetOf(columns)) + { + throw new FormattableException("Some columns are not mentioned in table definition: {0}", string.Join(",", dbColumns.Except(columns).ToArray())); + } + if(!dbColumns.IsSupersetOf(columns)) + { + throw new FormattableException("Some columns are missed in DB: {0}", string.Join(",", columns.Except(dbColumns).ToArray())); + } + } + } + + var options = (this as IDBTraits).GetColumnOptions(commandCreator, table.primaryKey.column); + /*Console.WriteLine(); + Console.WriteLine("'{0}' vs '{1}'", table.primaryKey.options.type, options.type); + Console.WriteLine("'{0}' vs '{1}'", table.primaryKey.options.defaultValue, options.defaultValue); + Console.WriteLine("'{0}' vs '{1}'", table.primaryKey.options.isNotNull, options.isNotNull);*/ + if(!table.primaryKey.options.Equals((this as IDBTraits).GetColumnOptions(commandCreator, table.primaryKey.column))) { + throw new FormattableException("Column {0} definition mismatch", table.primaryKey.column.columnName); + } + + foreach(var column in table.columns) { + options = (this as IDBTraits).GetColumnOptions(commandCreator, column.column); + /*Console.WriteLine(); + Console.WriteLine("'{0}' vs '{1}'", column.options.type, options.type); + Console.WriteLine("'{0}' vs '{1}'", column.options.defaultValue, options.defaultValue); + Console.WriteLine("'{0}' vs '{1}'", column.options.isNotNull, options.isNotNull);*/ + if(!column.options.Equals((this as IDBTraits).GetColumnOptions(commandCreator, column.column))) { + throw new FormattableException("Column {0} definition mismatch", column.column.columnName); + } + } } 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(); + ExecuteNonQuery(command); } } diff --git a/Patcher/DB/Transaction.cs b/Patcher/DB/Transaction.cs index 33d9d71..3840b73 100644 --- a/Patcher/DB/Transaction.cs +++ b/Patcher/DB/Transaction.cs @@ -162,6 +162,7 @@ namespace Patcher.DB { using(DbCommand command = this.CreateCommand(commandText, parameters)) { + Logger.instance.Log(commandText); using(DbDataReader reader = command.ExecuteReader()) { //Console.WriteLine(String.Join(";", (from i in Enumerable.Range(0, reader.FieldCount) select i + ":" + reader.GetName(i)).ToArray())); @@ -182,6 +183,7 @@ namespace Patcher.DB { using(DbCommand command = this.CreateCommand(commandText, parameters)) { + Logger.instance.Log(commandText); return command.ExecuteNonQuery(); } } diff --git a/Patcher/Updater.cs b/Patcher/Updater.cs index 6a230fc..8a65389 100644 --- a/Patcher/Updater.cs +++ b/Patcher/Updater.cs @@ -154,10 +154,10 @@ namespace Patcher var patchInstallInfo = transaction.ExecuteReader( String.Format( "select {1} from {0} where {2} = {4} and {3} = {5} for update", - this.context.PatchesTable, - "ROLLBACK_DATA", - "VERSION", - "NAME", + transaction.EscapeName(this.context.PatchesTable), + transaction.EscapeName("ROLLBACK_DATA"), + transaction.EscapeName("VERSION"), + transaction.EscapeName("NAME"), transaction.MarkParam("pversion"), transaction.MarkParam("pname") ), @@ -168,7 +168,6 @@ namespace Patcher } ).Single(); patch.Rollback(transaction, XDocument.Parse(patchInstallInfo["ROLLBACK_DATA"])); - System.Threading.Thread.Sleep(1000); int affectedRows = transaction.ExecuteNonQuery( String.Format( "delete from {0} where {1} = {3} and {2} = {4}", @@ -315,7 +314,7 @@ namespace Patcher patchesToRemove = ( from row in transaction.ExecuteReader( string.Format( - "select {1}, {2} from {0} for update order by {3} desc", + "select {1}, {2} from {0} order by {3} desc for update", transaction.EscapeName(this.context.PatchesTable), transaction.EscapeName("VERSION"), transaction.EscapeName("NAME"),