diff --git a/Builder/IISMainHandler/build.txt b/Builder/IISMainHandler/build.txt index 7730ef7..56749c8 100644 --- a/Builder/IISMainHandler/build.txt +++ b/Builder/IISMainHandler/build.txt @@ -1 +1 @@ -89 \ No newline at end of file +96 \ No newline at end of file diff --git a/Common/SqlObject.cs b/Common/SqlObject.cs index 4cdea91..93ff805 100644 --- a/Common/SqlObject.cs +++ b/Common/SqlObject.cs @@ -22,7 +22,11 @@ namespace FLocal.Common { abstract protected void doFromHash(Dictionary data); - protected void fromHash(Dictionary data) { + /// + /// Note that this method does not updates isLoaded field! + /// + /// + private void fromHash(Dictionary data) { lock(this.lockFiller) { if(data[this.table.idName] != this.id.ToString()) { throw new CriticalException("Id mismatch"); @@ -31,6 +35,10 @@ namespace FLocal.Common { } } + /// + /// Note that this method does not updates isLoaded field! + /// + /// private void doLoad() { this.fromHash(Config.instance.mainConnection.LoadById(this.table, this.id.ToString())); } @@ -43,17 +51,30 @@ namespace FLocal.Common { } } - protected void LoadIfNotLoaded() { + protected void LoadFromHash(Dictionary data) { lock(this.lockInitializer) { - if(!this.isLoaded) { - this.doLoad(); - this.isLoaded = true; + if(this.isLoaded) throw new CriticalException("already initialized"); + this.fromHash(data); + this.isLoaded = true; + } + } + + protected void LoadIfNotLoaded() { + if(!this.isLoaded) { + lock(this.lockInitializer) { + if(!this.isLoaded) { + this.doLoad(); + this.isLoaded = true; + } } } } public void ReLoad() { - this.doLoad(); + lock(this.lockInitializer) { + this.doLoad(); + this.isLoaded = true; + } } protected override void AfterCreate(bool forLoadingFromHash) { @@ -61,7 +82,7 @@ namespace FLocal.Common { if(!forLoadingFromHash) this.Load(); } - public static List LoadByIds(List ids) { + public static List LoadByIds(IEnumerable ids) { Dictionary rawRes = LoadByIdsForLoadingFromHash(ids); @@ -72,19 +93,23 @@ namespace FLocal.Common { } } + List loadedIds = new List(); if(idsToQuery.Count > 0) { ITableSpec table = rawRes[idsToQuery[0]].table; List> rawData = Config.instance.mainConnection.LoadByIds(table, new List(from int id in idsToQuery select id.ToString())); foreach(Dictionary row in rawData) { int id = int.Parse(row[table.idName]); + loadedIds.Add(id); if(!rawRes.ContainsKey(id)) throw new CriticalException("wrong id"); - rawRes[id].fromHash(row); + rawRes[id].LoadFromHash(row); } } List res = new List(); foreach(int id in ids) { - if(!rawRes[id].isLoaded) throw new CriticalException("not loaded"); + if(!rawRes[id].isLoaded) { + throw new CriticalException("#" + id + " not loaded (all ids (" + ids.ToPrintableString() + "), idsToQuery (" + idsToQuery.ToPrintableString() + "), loaded ids (" + loadedIds.ToPrintableString() + ")"); + } res.Add(rawRes[id]); } diff --git a/Common/dataobjects/Board.cs b/Common/dataobjects/Board.cs index 3aebe5f..5664b2a 100644 --- a/Common/dataobjects/Board.cs +++ b/Common/dataobjects/Board.cs @@ -7,64 +7,106 @@ using System.Xml.Linq; namespace FLocal.Common.dataobjects { public class Board : SqlObject { - private class TableSpec : FLocal.Core.DB.ITableSpec { + public class TableSpec : FLocal.Core.DB.ITableSpec { + public const string TABLE = "Boards"; + public const string FIELD_ID = "Id"; + public const string FIELD_SORTORDER = "SortOrder"; + public const string FIELD_CATEGORYID = "CategoryId"; + public const string FIELD_LASTPOSTID = "LastPostId"; + public const string FIELD_TOTALPOSTS = "TotalPosts"; + public const string FIELD_TOTALTHREADS = "TotalThreads"; + public const string FIELD_NAME = "Name"; + public const string FIELD_DESCRIPTION = "Comment"; public static readonly TableSpec instance = new TableSpec(); - public string name { get { return "Boards"; } } - public string idName { get { return "Id"; } } + public string name { get { return TABLE; } } + public string idName { get { return FIELD_ID; } } } protected override FLocal.Core.DB.ITableSpec table { get { return TableSpec.instance; } } - private string _name; - public string name { + private int _sortOrder; + public int sortOrder { get { this.LoadIfNotLoaded(); - return this._name; + return this._sortOrder; } } - - private string _description; - public string description { + + private int _categoryId; + public int categoryId { get { this.LoadIfNotLoaded(); - return this._description; + return this._categoryId; + } + } + public Category category { + get { + return Category.LoadById(this.categoryId); } } private int? _lastPostId; - public int lastPostId { + public int? lastPostId { get { this.LoadIfNotLoaded(); - return this._lastPostId.Value; + return this._lastPostId; } } - private int _categoryId; - public int categoryId { + private int _totalPosts; + public int totalPosts { get { this.LoadIfNotLoaded(); - return this._categoryId; + return this._totalPosts; } } - public Category category { + + private int _totalThreads; + public int totalThreads { get { - return Category.LoadById(this.categoryId); + this.LoadIfNotLoaded(); + return this._totalThreads; + } + } + + private string _name; + public string name { + get { + this.LoadIfNotLoaded(); + return this._name; + } + } + + private string _description; + public string description { + get { + this.LoadIfNotLoaded(); + return this._description; } } protected override void doFromHash(Dictionary data) { - this._name = data["Name"]; - this._description = data["Comment"]; - if(data["LastPostId"] != "") { - this._lastPostId = int.Parse(data["LastPostId"]); + this._sortOrder = int.Parse(data[TableSpec.FIELD_SORTORDER]); + this._categoryId = int.Parse(data[TableSpec.FIELD_CATEGORYID]); + if(data[TableSpec.FIELD_LASTPOSTID] != "") { + this._lastPostId = int.Parse(data[TableSpec.FIELD_LASTPOSTID]); } else { this._lastPostId = null; } - this._categoryId = int.Parse(data["CategoryId"]); + this._totalPosts = int.Parse(data[TableSpec.FIELD_TOTALPOSTS]); + this._totalThreads = int.Parse(data[TableSpec.FIELD_TOTALTHREADS]); + this._name = data[TableSpec.FIELD_NAME]; + this._description = data[TableSpec.FIELD_DESCRIPTION]; } - public XElement exportToXml() { + public XElement exportToXmlForMainPage() { return new XElement("board", + new XElement("id", this.id), + new XElement("sortOrder", this.sortOrder), + new XElement("categoryId", this.categoryId), + new XElement("lastPostId", this.lastPostId), + new XElement("totalPosts", this.totalPosts), + new XElement("totalThreads", this.totalThreads), new XElement("name", this.name), new XElement("description", this.description) ); diff --git a/Common/dataobjects/Category.cs b/Common/dataobjects/Category.cs index 71d4877..f39cdf0 100644 --- a/Common/dataobjects/Category.cs +++ b/Common/dataobjects/Category.cs @@ -2,17 +2,25 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using FLocal.Core; +using FLocal.Core.DB; +using System.Xml.Linq; namespace FLocal.Common.dataobjects { public class Category : SqlObject { - private class TableSpec : FLocal.Core.DB.ITableSpec { + public class TableSpec : ITableSpec { + public const string TABLE = "Categories"; + public const string FIELD_ID = "Id"; + public const string FIELD_SORTORDER = "SortOrder"; + public const string FIELD_NAME = "Name"; + public static readonly TableSpec instance = new TableSpec(); - public string name { get { return "Categories"; } } - public string idName { get { return "Id"; } } + public string name { get { return TABLE; } } + public string idName { get { return FIELD_ID; } } } - protected override FLocal.Core.DB.ITableSpec table { get { return TableSpec.instance; } } + protected override ITableSpec table { get { return TableSpec.instance; } } private string _name; public string name { @@ -22,8 +30,78 @@ namespace FLocal.Common.dataobjects { } } + private int _sortOrder; + public int sortOrder { + get { + this.LoadIfNotLoaded(); + return this._sortOrder; + } + } + protected override void doFromHash(Dictionary data) { - this._name = data["Name"]; + this._name = data[TableSpec.FIELD_NAME]; + this._sortOrder = int.Parse(data[TableSpec.FIELD_SORTORDER]); + } + + private static readonly object allCategories_Locker = new object(); + public static IEnumerable allCategories { + get { + return + from id in Cache>.instance.get( + allCategories_Locker, + () => { + IEnumerable ids = from stringId in Config.instance.mainConnection.LoadIdsByConditions( + TableSpec.instance, + new FLocal.Core.DB.conditions.EmptyCondition(), + Diapasone.unlimited, + new JoinSpec[0] + ) select int.Parse(stringId); + Category.LoadByIds(ids); + return ids; + } + ) + let category = Category.LoadById(id) + orderby category.sortOrder, category.id + select category; + } + } + internal static void allCategories_Reset() { + Cache>.instance.delete(allCategories_Locker); + } + + private readonly object subBoards_Locker = new object(); + public IEnumerable subBoards { + get { + return + from id in Cache>.instance.get( + this.subBoards_Locker, + () => { + IEnumerable ids = from stringId in Config.instance.mainConnection.LoadIdsByConditions( + Board.TableSpec.instance, + new FLocal.Core.DB.conditions.ComparisonCondition( + Board.TableSpec.instance.getColumnSpec(Board.TableSpec.FIELD_CATEGORYID), + FLocal.Core.DB.conditions.ComparisonType.EQUAL, + this.id.ToString() + ), + Diapasone.unlimited, + new JoinSpec[0] + ) select int.Parse(stringId); + Board.LoadByIds(ids); + return ids; + } + ) + let board = Board.LoadById(id) + orderby board.sortOrder, board.id + select board; + } + } + + public XElement exportToXmlForMainPage() { + return new XElement("category", + new XElement("name", this.name), + new XElement("sortOrder", this.sortOrder), + new XElement("boards", from board in this.subBoards select board.exportToXmlForMainPage()) + ); } } diff --git a/Core/Cache.cs b/Core/Cache.cs new file mode 100644 index 0000000..f574735 --- /dev/null +++ b/Core/Cache.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace FLocal.Core { + public class Cache { + + public static readonly Cache instance = new Cache(); + + private Dictionary cache; + + private Cache() { + this.cache = new Dictionary(); + } + + public T get(object id, Func getter) { + if(!this.cache.ContainsKey(id)) { + lock(id) { + if(!this.cache.ContainsKey(id)) { + this.cache[id] = getter(); + } + } + } + return this.cache[id]; + } + + public void delete(object id) { + lock(id) { + if(this.cache.ContainsKey(id)) { + this.cache.Remove(id); + } + } + } + + } +} diff --git a/Core/Core.csproj b/Core/Core.csproj index 0da598b..48ba9f2 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -45,6 +45,7 @@ + @@ -54,8 +55,10 @@ + + diff --git a/Core/DB/Diapasone.cs b/Core/DB/Diapasone.cs index 164751b..6646816 100644 --- a/Core/DB/Diapasone.cs +++ b/Core/DB/Diapasone.cs @@ -6,17 +6,23 @@ using System.Text; namespace FLocal.Core.DB { public class Diapasone { - public readonly int start; - public readonly int count; - public int total { + public readonly long start; + public readonly long count; + public long total { get; set; } - public Diapasone(int start, int count) { + public Diapasone(long start, long count) { this.start = start; this.count = count; } + public static Diapasone unlimited { + get { + return new Diapasone(0, -1); + } + } + } } diff --git a/Core/DB/ITableSpec.cs b/Core/DB/ITableSpec.cs index 8b4eac3..1d90875 100644 --- a/Core/DB/ITableSpec.cs +++ b/Core/DB/ITableSpec.cs @@ -21,6 +21,10 @@ namespace FLocal.Core.DB { return new ColumnSpec(table, table.idName); } + public static ColumnSpec getColumnSpec(this ITableSpec table, string column) { + return new ColumnSpec(table, column); + } + } } diff --git a/Core/DB/conditions/ComplexCondition.cs b/Core/DB/conditions/ComplexCondition.cs index 8af4c42..12d2a14 100644 --- a/Core/DB/conditions/ComplexCondition.cs +++ b/Core/DB/conditions/ComplexCondition.cs @@ -4,13 +4,13 @@ using System.Linq; using System.Text; namespace FLocal.Core.DB.conditions { - public class ComplexCondition : AbstractCondition { + public class ComplexCondition : NotEmptyCondition { public readonly ConditionsJoinType type; - public readonly List innerConditions; + public readonly List innerConditions; - public ComplexCondition(ConditionsJoinType type, List innerConditions) { + public ComplexCondition(ConditionsJoinType type, List innerConditions) { this.type = type; this.innerConditions = innerConditions; } diff --git a/Core/DB/conditions/SimpleCondition.cs b/Core/DB/conditions/SimpleCondition.cs index acadaca..90fbf0b 100644 --- a/Core/DB/conditions/SimpleCondition.cs +++ b/Core/DB/conditions/SimpleCondition.cs @@ -4,6 +4,6 @@ using System.Linq; using System.Text; namespace FLocal.Core.DB.conditions { - public abstract class SimpleCondition : AbstractCondition { + public abstract class SimpleCondition : NotEmptyCondition { } } diff --git a/Core/DataObject.cs b/Core/DataObject.cs index d7ad6e3..69a5ec0 100644 --- a/Core/DataObject.cs +++ b/Core/DataObject.cs @@ -39,7 +39,7 @@ namespace FLocal.Core { return registry.Get(id, false); } - protected static Dictionary LoadByIdsForLoadingFromHash(List ids) { + protected static Dictionary LoadByIdsForLoadingFromHash(IEnumerable ids) { Dictionary res = new Dictionary(); foreach(TKey id in ids) { res[id] = registry.Get(id, true); diff --git a/Core/extensions/Extensions.cs b/Core/extensions/Extensions.cs index 700095d..37dde1a 100644 --- a/Core/extensions/Extensions.cs +++ b/Core/extensions/Extensions.cs @@ -5,7 +5,7 @@ using System.Text; namespace FLocal.Core { - static class ExtensionMethods { + public static class ExtensionMethods { public static Dictionary ToDictionary(this IEnumerable> kvps) { @@ -49,6 +49,10 @@ namespace FLocal.Core { return val ? "Enabled" : "Disabled"; } + public static string ToPrintableString(this IEnumerable list) { + return string.Join(",", (from elem in list select elem.ToString()).ToArray()); + } + public static string ToXmlApiRequestString(this bool val) { return val ? "1" : "0"; } @@ -202,7 +206,7 @@ namespace FLocal.Core { return from elem in list select replacer(elem); } - public static string ToStringOnFail(this bool test, Lazy onFail) { + internal static string ToStringOnFail(this bool test, Lazy onFail) { return test ? "" : onFail(); } diff --git a/IISMainHandler/handlers/BoardsHandler.cs b/IISMainHandler/handlers/BoardsHandler.cs index 38cc3e2..10af8d9 100644 --- a/IISMainHandler/handlers/BoardsHandler.cs +++ b/IISMainHandler/handlers/BoardsHandler.cs @@ -24,21 +24,7 @@ namespace FLocal.IISHandler.handlers { return new XDocument( new XElement("root", new XElement("title", Config.instance.AppInfo), - new XElement("categories", - new XElement("category", - new XElement("name", board1.category.name), - new XElement("boards", - board1.exportToXml(), - board2.exportToXml() - ) - ), - new XElement("category", - new XElement("name", board3.category.name), - new XElement("boards", - board3.exportToXml() - ) - ) - ) + new XElement("categories", from category in Category.allCategories select category.exportToXmlForMainPage()) ) ); } diff --git a/MySQLConnector/ConditionCompiler.cs b/MySQLConnector/ConditionCompiler.cs index 475988f..3a639c1 100644 --- a/MySQLConnector/ConditionCompiler.cs +++ b/MySQLConnector/ConditionCompiler.cs @@ -82,7 +82,7 @@ namespace FLocal.MySQLConnector { private string CompileCondition(ComplexCondition condition) { List parts = new List(); - foreach(AbstractCondition innerCondition in condition.innerConditions) { + foreach(NotEmptyCondition innerCondition in condition.innerConditions) { parts.Add("(" + CompileCondition(innerCondition) + ")"); } @@ -96,7 +96,7 @@ namespace FLocal.MySQLConnector { } } - private string CompileCondition(AbstractCondition condition) { + private string CompileCondition(NotEmptyCondition condition) { if(condition is ComplexCondition) { return CompileCondition((ComplexCondition)condition); } else if(condition is SimpleCondition) { @@ -106,6 +106,20 @@ namespace FLocal.MySQLConnector { } } + private string CompileCondition(EmptyCondition condition) { + return ""; + } + + private string CompileCondition(AbstractCondition condition) { + if(condition is NotEmptyCondition) { + return CompileCondition((NotEmptyCondition)condition); + } else if(condition is EmptyCondition) { + return CompileCondition((EmptyCondition)condition); + } else { + throw new NotImplementedException(); + } + } + public static KeyValuePair Compile(AbstractCondition condition, IDBTraits traits) { ConditionCompiler compiler = new ConditionCompiler(traits); string compiled = compiler.CompileCondition(condition); diff --git a/MySQLConnector/Connection.cs b/MySQLConnector/Connection.cs index 0c1ef0b..6b42153 100644 --- a/MySQLConnector/Connection.cs +++ b/MySQLConnector/Connection.cs @@ -42,7 +42,7 @@ namespace FLocal.MySQLConnector { placeholder.Add(this.traits.markParam(paramsHolder.Add(id))); } - command.CommandText = "SELECT * FROM " + table.compile(this.traits) + " WHERE " + table.getIdSpec().compile(this.traits) + " = " + string.Join(", ", placeholder.ToArray()) + ""; + command.CommandText = "SELECT * FROM " + table.compile(this.traits) + " WHERE " + table.getIdSpec().compile(this.traits) + " IN (" + string.Join(", ", placeholder.ToArray()) + ")"; //command.Prepare(); foreach(KeyValuePair kvp in paramsHolder.data) { command.AddParameter(kvp.Key, kvp.Value); @@ -78,7 +78,8 @@ namespace FLocal.MySQLConnector { command.CommandType = System.Data.CommandType.Text; var conditionsCompiled = ConditionCompiler.Compile(conditions, this.traits); - string queryConditions = conditionsCompiled.Key; + string queryConditions = ""; + if(conditionsCompiled.Key != "") queryConditions = "WHERE " + conditionsCompiled.Key; ParamsHolder paramsHolder = conditionsCompiled.Value; string queryJoins = ""; @@ -89,13 +90,19 @@ namespace FLocal.MySQLConnector { } string querySorts = ""; - { - if(sorts.Length > 0) { - throw new NotImplementedException(); + if(sorts.Length > 0) { + List sortParts = new List(); + foreach(SortSpec sortSpec in sorts) { + if(sortSpec.ascending) { + sortParts.Add(sortSpec.column.compile(this.traits) + " ASC"); + } else { + sortParts.Add(sortSpec.column.compile(this.traits) + " DESC"); + } } + querySorts = "ORDER BY " + string.Join(", ", sortParts.ToArray()); } - string queryMain = "FROM " + table.compile(this.traits) + " " + queryJoins + " WHERE " + queryConditions; + string queryMain = "FROM " + table.compile(this.traits) + " " + queryJoins + " " + queryConditions; foreach(KeyValuePair kvp in paramsHolder.data) { command.AddParameter(kvp.Key, kvp.Value); @@ -103,7 +110,7 @@ namespace FLocal.MySQLConnector { command.CommandText = "SELECT COUNT(*) " + queryMain; object rawCount = command.ExecuteScalar(); - int count = (int)rawCount; + long count = (long)rawCount; if(count < 1) { diapasone.total = 0; return new List();