Browse Source

Completed integrating Patcher with FLocal; some PostgresDBTraits features are not implemented yet

main
Inga 🏳‍🌈 11 years ago
parent
commit
dc497fd99c
  1. 2
      Builder/IISMainHandler/build.txt
  2. 28
      FLocal.IISHandler/Initializer.cs
  3. 3
      FLocal.Patcher.IISHandler/MainHandler.cs
  4. 75
      Patcher.Web/MainHandler.cs
  5. 19
      Patcher.Web/PatcherInfo.cs
  6. 42
      Patcher/Checker.cs
  7. 22
      Patcher/Context.cs
  8. 146
      Patcher/DB/PostgresDBTraits.cs
  9. 2
      Patcher/DB/Transaction.cs
  10. 11
      Patcher/Updater.cs

2
Builder/IISMainHandler/build.txt

@ -1 +1 @@ @@ -1 +1 @@
1985
1996

28
FLocal.IISHandler/Initializer.cs

@ -17,6 +17,7 @@ namespace FLocal.IISHandler { @@ -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 { @@ -25,6 +26,7 @@ namespace FLocal.IISHandler {
/// </summary>
private Initializer() {
this.isInitialized = false;
this.isCached = false;
}
public void Initialize() {
@ -36,21 +38,33 @@ namespace FLocal.IISHandler { @@ -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<KeyValuePair<string, Action>> cachers {

3
FLocal.Patcher.IISHandler/MainHandler.cs

@ -15,7 +15,8 @@ namespace FLocal.Patcher.IISHandler { @@ -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]);
}
}

75
Patcher.Web/MainHandler.cs

@ -3,10 +3,14 @@ using System.Collections.Generic; @@ -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 { @@ -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("<form method=\"POST\">");
writer.WriteLine("<input type=\"password\" name=\"data\"/><br/>");
writer.WriteLine("<input type=\"hidden\" name=\"{0}\" value=\"{0}\"/>", method);
writer.WriteLine("<input type=\"submit\" value=\"{0}\"/>", methodDescription);
writer.WriteLine("</form>");
}
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("<p>{0}: \"{1}\"</p>", patchId.version, patchId.name);
totalPatches++;
{
writer.WriteLine("<h1>Patches to install</h1>");
int totalPatches = 0;
writer.WriteLine("<ol>");
foreach(var patchId in checker.GetPatchesToInstall()) {
writer.WriteLine("<li>{0}: \"{1}\"</li>", patchId.version, patchId.name);
totalPatches++;
}
writer.WriteLine("</ol>");
writer.WriteLine("<p>Total patches: {0}</p>", totalPatches);
if(totalPatches > 0) {
WriteForm(writer, ACTION_INSTALLALL, "Install all patches");
}
}
writer.WriteLine("<p>Total patches: {0}", totalPatches);
if(totalPatches > 0) {
writer.WriteLine("<form method=\"POST\">");
writer.WriteLine("<input type=\"text\" name=\"data\"/><br/>");
writer.WriteLine("<input type=\"hidden\" name=\"install\" value=\"install\"/>");
writer.WriteLine("<input type=\"submit\" value=\"Install!\"/>");
writer.WriteLine("</form>");
{
writer.WriteLine("<h1>Installed patches</h1>");
int totalPatches = 0;
writer.WriteLine("<ol>");
foreach(var patchId in checker.GetInstalledPatches()) {
writer.WriteLine("<li>{0}: \"{1}\"</li>", patchId.version, patchId.name);
totalPatches++;
}
writer.WriteLine("</ol>");
writer.WriteLine("<p>Total patches: {0}</p>", 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);
}

19
Patcher.Web/PatcherInfo.cs

@ -8,22 +8,33 @@ namespace Patcher.Web { @@ -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;
}
}

42
Patcher/Checker.cs

@ -25,28 +25,34 @@ namespace Patcher { @@ -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<string, object> {
{ "pstatus", STATUS_INSTALLED },
}
)
let patch = new PatchId(int.Parse(row["VERSION"]), row["NAME"])
orderby patch ascending
select patch
).ToList()
this.GetInstalledPatches()
);
}
}
public IEnumerable<PatchId> 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<string, object> {
{ "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();
}

22
Patcher/Context.cs

@ -49,4 +49,26 @@ namespace Patcher @@ -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();
}
}
}
}

146
Patcher/DB/PostgresDBTraits.cs

@ -13,6 +13,16 @@ namespace Patcher.DB { @@ -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 { @@ -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<T>(object value) where T : class {
if(DBNull.Value.Equals(value)) {
return null;
@ -115,29 +134,42 @@ namespace Patcher.DB { @@ -115,29 +134,42 @@ namespace Patcher.DB {
}
ColumnOptions IDBTraits.GetColumnOptions(Func<DbCommand> 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<DbCommand> commandCreator, ColumnReference column) {
using(DbCommand command = commandCreator()) {
command.CommandText = _SQLQueryManager.RemoveColumn(column);
command.ExecuteNonQuery();
ExecuteNonQuery(command);
}
}
void IDBTraits.CreateColumn(Func<DbCommand> commandCreator, ColumnDescription description) {
using(DbCommand command = commandCreator()) {
command.CommandText = _SQLQueryManager.CreateColumn(description);
command.ExecuteNonQuery();
ExecuteNonQuery(command);
}
}
void IDBTraits.ModifyColumn(Func<DbCommand> 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 { @@ -157,14 +189,53 @@ namespace Patcher.DB {
}
private void CheckConstraint(Func<DbCommand> 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<reader.FieldCount; j++) {
Logger.instance.Log(reader.GetName(j) + "='" + reader.GetValue(j) + "'");
}
row++;
}
}
}
throw new NotImplementedException();
}
private void CheckConstraint(Func<DbCommand> 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<reader.FieldCount; j++) {
Logger.instance.Log(reader.GetName(j) + "='" + reader.GetValue(j) + "'");
}
row++;
}
}
}
throw new NotImplementedException();
}
private void CheckConstraint(Func<DbCommand> 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<reader.FieldCount; j++) {
Logger.instance.Log(reader.GetName(j) + "='" + reader.GetValue(j) + "'");
}
row++;
}
}
}
throw new NotImplementedException();
}
@ -176,41 +247,78 @@ namespace Patcher.DB { @@ -176,41 +247,78 @@ namespace Patcher.DB {
CheckConstraint(commandCreator, constraint);
using(DbCommand command = commandCreator()) {
command.CommandText = _SQLQueryManager.DropConstraint(constraint);
Console.WriteLine();
Console.WriteLine(command.CommandText);
command.ExecuteNonQuery();
ExecuteNonQuery(command);
}
}
public void CreateConstraint(Func<DbCommand> 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<DbCommand> 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<DbCommand> commandCreator, TableDescription table) {
throw new NotImplementedException();
HashSet<string> columns = new HashSet<string>(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<string> dbColumns = new HashSet<string>();
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<DbCommand> 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);
}
}

2
Patcher/DB/Transaction.cs

@ -162,6 +162,7 @@ namespace Patcher.DB @@ -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 @@ -182,6 +183,7 @@ namespace Patcher.DB
{
using(DbCommand command = this.CreateCommand(commandText, parameters))
{
Logger.instance.Log(commandText);
return command.ExecuteNonQuery();
}
}

11
Patcher/Updater.cs

@ -154,10 +154,10 @@ namespace Patcher @@ -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 @@ -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 @@ -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"),

Loading…
Cancel
Save