From a314b252cfa0aa6dcc0093114ee293f1a8148889 Mon Sep 17 00:00:00 2001 From: inga-lovinde <52715130+inga-lovinde@users.noreply.github.com> Date: Fri, 25 Jun 2010 19:11:57 +0000 Subject: [PATCH] Implemented readmarkers for threads; implemented LoadIdsByConditions within transaction; refactored actions; InsertOrUpdateChange now takes dataToInsert and DataToUpdate; fixed a deadlock in InsertOrUpdateChange --- Builder/IISMainHandler/build.txt | 2 +- Builder/IISUploadHandler/build.txt | 2 +- Common/actions/AbstractChange.cs | 7 +- Common/actions/ChangeSet.cs | 17 +- Common/actions/InsertChange.cs | 5 +- Common/actions/InsertOrUpdateChange.cs | 24 ++- Common/actions/UpdateChange.cs | 4 +- Common/dataobjects/Post.cs | 5 +- Common/dataobjects/Thread.cs | 143 ++++++++++++++++- Core/DB/IDBConnection.cs | 2 + Core/DB/conditions/ComplexCondition.cs | 4 +- IISMainHandler/handlers/BoardHandler.cs | 2 +- IISMainHandler/handlers/PostHandler.cs | 10 +- IISMainHandler/handlers/ThreadHandler.cs | 35 ++-- MySQLConnector/Connection.cs | 193 ++++++++++++----------- templates/Full/elems/PostInfo.xslt | 15 +- templates/Full/elems/ThreadInfo.xslt | 12 +- 17 files changed, 346 insertions(+), 136 deletions(-) diff --git a/Builder/IISMainHandler/build.txt b/Builder/IISMainHandler/build.txt index 98c1572..d35d5f7 100644 --- a/Builder/IISMainHandler/build.txt +++ b/Builder/IISMainHandler/build.txt @@ -1 +1 @@ -327 \ No newline at end of file +347 \ No newline at end of file diff --git a/Builder/IISUploadHandler/build.txt b/Builder/IISUploadHandler/build.txt index fc9afb4..eb13855 100644 --- a/Builder/IISUploadHandler/build.txt +++ b/Builder/IISUploadHandler/build.txt @@ -1 +1 @@ -59 \ No newline at end of file +79 \ No newline at end of file diff --git a/Common/actions/AbstractChange.cs b/Common/actions/AbstractChange.cs index 31feebb..31b77c6 100644 --- a/Common/actions/AbstractChange.cs +++ b/Common/actions/AbstractChange.cs @@ -26,12 +26,9 @@ namespace FLocal.Common.actions { public readonly ISqlObjectTableSpec tableSpec; - protected readonly Dictionary data; - - protected AbstractChange(ISqlObjectTableSpec tableSpec, Dictionary data) { + protected AbstractChange(ISqlObjectTableSpec tableSpec, IEnumerable data) { this.tableSpec = tableSpec; - this.data = data; - this.references = from kvp in data where kvp.Value is ReferenceFieldValue select ((ReferenceFieldValue)kvp.Value).referenced; + this.references = from val in data where val is ReferenceFieldValue select ((ReferenceFieldValue)val).referenced; this.isApplied = false; } diff --git a/Common/actions/ChangeSet.cs b/Common/actions/ChangeSet.cs index 1f5d707..749846d 100644 --- a/Common/actions/ChangeSet.cs +++ b/Common/actions/ChangeSet.cs @@ -19,12 +19,13 @@ namespace FLocal.Common.actions { return Cache>.instance.get( tablesLockOrder_locker, () => new List() { - "Accounts", - "Users", - "Boards", - "Threads", - "Posts", - "Sessions", + dataobjects.Account.TableSpec.TABLE, + dataobjects.User.TableSpec.TABLE, + dataobjects.Board.TableSpec.TABLE, + dataobjects.Thread.TableSpec.TABLE, + dataobjects.Post.TableSpec.TABLE, + dataobjects.Thread.ReadMarkerTableSpec.TABLE, + dataobjects.Session.TableSpec.TABLE, } ); } @@ -94,7 +95,9 @@ namespace FLocal.Common.actions { //if(!this.isProcessed) throw new CriticalException("ChangeSet is not processed yet"); foreach(KeyValuePair> kvp in this.changesByTable) { foreach(AbstractChange change in kvp.Value) { - change.tableSpec.refreshSqlObject(change.getId().Value); + if(change.getId().HasValue) { + change.tableSpec.refreshSqlObject(change.getId().Value); + } //otherwise we're disposing because of sql error or something, so we should show real cause of problem, not "id is null" } } } diff --git a/Common/actions/InsertChange.cs b/Common/actions/InsertChange.cs index e540f1b..304ac3d 100644 --- a/Common/actions/InsertChange.cs +++ b/Common/actions/InsertChange.cs @@ -9,9 +9,12 @@ namespace FLocal.Common.actions { private int? id; + private Dictionary data; + public InsertChange(ISqlObjectTableSpec tableSpec, Dictionary data) - : base(tableSpec, data) { + : base(tableSpec, from kvp in data select kvp.Value) { this.id = null; + this.data = data; } public override int? getId() { diff --git a/Common/actions/InsertOrUpdateChange.cs b/Common/actions/InsertOrUpdateChange.cs index 3421ca7..2d6956c 100644 --- a/Common/actions/InsertOrUpdateChange.cs +++ b/Common/actions/InsertOrUpdateChange.cs @@ -13,10 +13,15 @@ namespace FLocal.Common.actions { private AbstractCondition condition; - public InsertOrUpdateChange(ISqlObjectTableSpec tableSpec, Dictionary data, AbstractCondition condition) - : base(tableSpec, data) { + private Dictionary dataToInsert; + private Dictionary dataToUpdate; + + public InsertOrUpdateChange(ISqlObjectTableSpec tableSpec, Dictionary dataToInsert, Dictionary dataToUpdate, AbstractCondition condition) + : base(tableSpec, (from kvp in dataToInsert select kvp.Value).Union(from kvp in dataToUpdate select kvp.Value)) { this.id = null; this.condition = condition; + this.dataToInsert = dataToInsert; + this.dataToUpdate = dataToUpdate; } public override int? getId() { @@ -32,7 +37,7 @@ namespace FLocal.Common.actions { Config.instance.mainConnection.lockRow(transaction, this.tableSpec, this.id.ToString()); } else { Config.instance.mainConnection.lockTable(transaction, this.tableSpec); - ids = Config.instance.mainConnection.LoadIdsByConditions(this.tableSpec, this.condition, Diapasone.unlimited, new JoinSpec[0]); + ids = Config.instance.mainConnection.LoadIdsByConditions(transaction, this.tableSpec, this.condition, Diapasone.unlimited, new JoinSpec[0], new SortSpec[0], false); if(ids.Count > 1) { throw new CriticalException("Not unique"); } else if(ids.Count == 1) { @@ -44,11 +49,12 @@ namespace FLocal.Common.actions { } protected override void doApply(Transaction transaction) { - Dictionary processedData = new Dictionary(); - foreach(KeyValuePair kvp in this.data) { - processedData[kvp.Key] = kvp.Value.getStringRepresentation(); - } if(this.id.HasValue) { + Dictionary row = Config.instance.mainConnection.LoadByIds(transaction, this.tableSpec, new List() { this.id.ToString() })[0]; + Dictionary processedData = new Dictionary(); + foreach(KeyValuePair kvp in this.dataToUpdate) { + processedData[kvp.Key] = kvp.Value.getStringRepresentation(row[kvp.Key]); + } Config.instance.mainConnection.update( transaction, this.tableSpec, @@ -56,6 +62,10 @@ namespace FLocal.Common.actions { processedData ); } else { + Dictionary processedData = new Dictionary(); + foreach(KeyValuePair kvp in this.dataToInsert) { + processedData[kvp.Key] = kvp.Value.getStringRepresentation(); + } Config.instance.mainConnection.insert( transaction, this.tableSpec, diff --git a/Common/actions/UpdateChange.cs b/Common/actions/UpdateChange.cs index 079b51a..b99e1f6 100644 --- a/Common/actions/UpdateChange.cs +++ b/Common/actions/UpdateChange.cs @@ -8,10 +8,12 @@ namespace FLocal.Common.actions { public class UpdateChange : AbstractChange { private readonly int id; + private Dictionary data; public UpdateChange(ISqlObjectTableSpec tableSpec, Dictionary data, int id) - : base(tableSpec, data) { + : base(tableSpec, from kvp in data select kvp.Value) { this.id = id; + this.data = data; } public override int? getId() { diff --git a/Common/dataobjects/Post.cs b/Common/dataobjects/Post.cs index f6cac0e..4ea6512 100644 --- a/Common/dataobjects/Post.cs +++ b/Common/dataobjects/Post.cs @@ -142,7 +142,7 @@ namespace FLocal.Common.dataobjects { ); } - public XElement exportToXmlWithoutThread(UserContext context, bool includeParentPost) { + public XElement exportToXmlWithoutThread(UserContext context, bool includeParentPost, params XElement[] additional) { XElement result = new XElement("post", new XElement("id", this.id), new XElement("poster", this.poster.exportToXmlForViewing(context)), @@ -160,6 +160,9 @@ namespace FLocal.Common.dataobjects { result.Add(new XElement("parentPost", this.parentPost.exportToXmlWithoutThread(context, false))); } } + if(additional.Length > 0) { + result.Add(additional); + } return result; } diff --git a/Common/dataobjects/Thread.cs b/Common/dataobjects/Thread.cs index f6e7a2b..db5216f 100644 --- a/Common/dataobjects/Thread.cs +++ b/Common/dataobjects/Thread.cs @@ -30,6 +30,18 @@ namespace FLocal.Common.dataobjects { public void refreshSqlObject(int id) { Refresh(id); } } + public class ReadMarkerTableSpec : ISqlObjectTableSpec { + public const string TABLE = "Threads_ReadMarkers"; + public const string FIELD_ID = "Id"; + public const string FIELD_THREADID = "ThreadId"; + public const string FIELD_ACCOUNTID = "AccountId"; + public const string FIELD_POSTID = "PostId"; + public static readonly ReadMarkerTableSpec instance = new ReadMarkerTableSpec(); + public string name { get { return TABLE; } } + public string idName { get { return FIELD_ID; } } + public void refreshSqlObject(int id) { } + } + protected override ISqlObjectTableSpec table { get { return TableSpec.instance; } } private int _boardId; @@ -152,7 +164,7 @@ namespace FLocal.Common.dataobjects { ); } - public XElement exportToXml(UserContext context, bool includeFirstPost) { + public XElement exportToXml(UserContext context, bool includeFirstPost, params XElement[] additional) { XElement result = new XElement("thread", new XElement("id", this.id), new XElement("firstPostId", this.firstPostId), @@ -164,13 +176,15 @@ namespace FLocal.Common.dataobjects { new XElement("isLocked", this.isLocked), new XElement("totalPosts", this.totalPosts), new XElement("totalViews", this.totalViews), - new XElement("hasNewPosts", this.hasNewPosts()), new XElement("bodyShort", this.firstPost.bodyShort), context.formatTotalPosts(this.totalPosts) ); if(includeFirstPost) { result.Add(new XElement("firstPost", this.firstPost.exportToXmlWithoutThread(context, false))); } + if(additional.Length > 0) { + result.Add(additional); + } return result; } @@ -210,6 +224,131 @@ namespace FLocal.Common.dataobjects { }); } + private Post getLastRead(Account account) { + List stringIds = Config.instance.mainConnection.LoadIdsByConditions( + ReadMarkerTableSpec.instance, + new ComplexCondition( + ConditionsJoinType.AND, + new ComparisonCondition( + ReadMarkerTableSpec.instance.getColumnSpec(ReadMarkerTableSpec.FIELD_THREADID), + ComparisonType.EQUAL, + this.id.ToString() + ), + new ComparisonCondition( + ReadMarkerTableSpec.instance.getColumnSpec(ReadMarkerTableSpec.FIELD_ACCOUNTID), + ComparisonType.EQUAL, + account.id.ToString() + ) + ), + Diapasone.unlimited + ); + if(stringIds.Count > 1) { + throw new CriticalException("more than one row"); + } + if(stringIds.Count < 1) { + return null; + } + Dictionary data = Config.instance.mainConnection.LoadById(ReadMarkerTableSpec.instance, stringIds[0]); + if((data[ReadMarkerTableSpec.FIELD_POSTID] == "") || (data[ReadMarkerTableSpec.FIELD_POSTID] == null)) { + return null; + } + return Post.LoadById(int.Parse(data[ReadMarkerTableSpec.FIELD_POSTID])); + } + + public int getLastReadId(Session session) { + if(session == null) { + return 0; + } + Post post = this.getLastRead(session.account); + if(post == null) { + return 0; + } + return post.id; + } + + public void markAsRead(Account account, Post minPost, Post maxPost) { + ChangeSetUtil.ApplyChanges(new AbstractChange[] { + new InsertOrUpdateChange( + ReadMarkerTableSpec.instance, + new Dictionary { + { + ReadMarkerTableSpec.FIELD_THREADID, + new ScalarFieldValue(this.id.ToString()) + }, + { + ReadMarkerTableSpec.FIELD_ACCOUNTID, + new ScalarFieldValue(account.id.ToString()) + }, + { + ReadMarkerTableSpec.FIELD_POSTID, + new ScalarFieldValue( + (minPost.id < this.firstPostId) + ? + maxPost.id.ToString() + : + null + ) }, + }, + new Dictionary { + { + ReadMarkerTableSpec.FIELD_POSTID, + new IncrementFieldValue( + s => { + if((s == null) || (s == "")) { + s = "0"; //workaround + } + if(maxPost.id < int.Parse(s)) { + return (s == "0") ? null : s; //if some newer posts were already read + } + long count = Config.instance.mainConnection.GetCountByConditions( + Post.TableSpec.instance, + new ComplexCondition( + ConditionsJoinType.AND, + new ComparisonCondition( + Post.TableSpec.instance.getColumnSpec(Post.TableSpec.FIELD_THREADID), + ComparisonType.EQUAL, + this.id.ToString() + ), + new ComparisonCondition( + Post.TableSpec.instance.getIdSpec(), + ComparisonType.GREATERTHAN, + s + ), + new ComparisonCondition( + Post.TableSpec.instance.getIdSpec(), + ComparisonType.LESSTHAN, + minPost.id.ToString() + ) + ), + new JoinSpec[0] + ); + if(count > 0) { + return (s == "0") ? null : s; //if there are some unread posts earlier than minPost + } else { + return maxPost.id.ToString(); + } + } + ) + } + + }, + new ComplexCondition( + ConditionsJoinType.AND, + new ComparisonCondition( + ReadMarkerTableSpec.instance.getColumnSpec(ReadMarkerTableSpec.FIELD_THREADID), + ComparisonType.EQUAL, + this.id.ToString() + ), + new ComparisonCondition( + ReadMarkerTableSpec.instance.getColumnSpec(ReadMarkerTableSpec.FIELD_ACCOUNTID), + ComparisonType.EQUAL, + account.id.ToString() + ) + ) + ) + }); + } + } } diff --git a/Core/DB/IDBConnection.cs b/Core/DB/IDBConnection.cs index fdab8a3..109ec72 100644 --- a/Core/DB/IDBConnection.cs +++ b/Core/DB/IDBConnection.cs @@ -20,6 +20,8 @@ namespace FLocal.Core.DB { List> LoadByIds(Transaction transaction, ITableSpec table, List ids); + List LoadIdsByConditions(Transaction transaction, ITableSpec table, conditions.AbstractCondition conditions, Diapasone diapasone, JoinSpec[] joins, SortSpec[] sorts, bool allowHugeLists); + void update(Transaction transaction, ITableSpec table, string id, Dictionary data); string insert(Transaction transaction, ITableSpec table, Dictionary data); diff --git a/Core/DB/conditions/ComplexCondition.cs b/Core/DB/conditions/ComplexCondition.cs index 12d2a14..87a08c1 100644 --- a/Core/DB/conditions/ComplexCondition.cs +++ b/Core/DB/conditions/ComplexCondition.cs @@ -8,9 +8,9 @@ namespace FLocal.Core.DB.conditions { public readonly ConditionsJoinType type; - public readonly List innerConditions; + public readonly NotEmptyCondition[] innerConditions; - public ComplexCondition(ConditionsJoinType type, List innerConditions) { + public ComplexCondition(ConditionsJoinType type, params NotEmptyCondition[] innerConditions) { this.type = type; this.innerConditions = innerConditions; } diff --git a/IISMainHandler/handlers/BoardHandler.cs b/IISMainHandler/handlers/BoardHandler.cs index 19f56fb..e8cd4c3 100644 --- a/IISMainHandler/handlers/BoardHandler.cs +++ b/IISMainHandler/handlers/BoardHandler.cs @@ -26,7 +26,7 @@ namespace FLocal.IISHandler.handlers { new XElement("currentLocation", board.exportToXmlSimpleWithParent(context)), new XElement("boards", from subBoard in board.subBoards select subBoard.exportToXml(context, true)), new XElement("threads", - (from thread in threads select thread.exportToXml(context, false)).addNumbers(), + (from thread in threads select thread.exportToXml(context, false, new XElement("afterLastRead", thread.getLastReadId(context.session) + 1))).addNumbers(), pageOuter.exportToXml(1, 5, 1) ) }; diff --git a/IISMainHandler/handlers/PostHandler.cs b/IISMainHandler/handlers/PostHandler.cs index 2bbc9dc..0b51bd0 100644 --- a/IISMainHandler/handlers/PostHandler.cs +++ b/IISMainHandler/handlers/PostHandler.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Web; using System.Xml.Linq; +using FLocal.Core; using FLocal.Common; using FLocal.Common.dataobjects; @@ -19,10 +20,17 @@ namespace FLocal.IISHandler.handlers { override protected XElement[] getSpecificData(WebContext context) { Post post = Post.LoadById(int.Parse(context.requestParts[1])); + + int lastReadId = post.thread.getLastReadId(context.session); + post.thread.incrementViewsCounter(); + if(context.session != null) { + post.thread.markAsRead(context.session.account, post, post); + } + return new XElement[] { new XElement("currentLocation", post.exportToXmlSimpleWithParent(context)), - new XElement("posts", post.exportToXmlWithoutThread(context, true)) + new XElement("posts", post.exportToXmlWithoutThread(context, true, new XElement("isUnread", (post.id > lastReadId).ToPlainString()))) }; } diff --git a/IISMainHandler/handlers/ThreadHandler.cs b/IISMainHandler/handlers/ThreadHandler.cs index adbecd5..be09504 100644 --- a/IISMainHandler/handlers/ThreadHandler.cs +++ b/IISMainHandler/handlers/ThreadHandler.cs @@ -32,18 +32,16 @@ namespace FLocal.IISHandler.handlers { Post.TableSpec.instance, new ComplexCondition( ConditionsJoinType.AND, - new List { - new ComparisonCondition( - Post.TableSpec.instance.getColumnSpec(Post.TableSpec.FIELD_THREADID), - ComparisonType.EQUAL, - thread.id.ToString() - ), - new ComparisonCondition( - Post.TableSpec.instance.getIdSpec(), - ComparisonType.LESSTHAN, - int.Parse(context.requestParts[2].PHPSubstring(1)).ToString() - ), - } + new ComparisonCondition( + Post.TableSpec.instance.getColumnSpec(Post.TableSpec.FIELD_THREADID), + ComparisonType.EQUAL, + thread.id.ToString() + ), + new ComparisonCondition( + Post.TableSpec.instance.getIdSpec(), + ComparisonType.LESSTHAN, + int.Parse(context.requestParts[2].PHPSubstring(1)).ToString() + ) ), new JoinSpec[0] ) @@ -52,11 +50,22 @@ namespace FLocal.IISHandler.handlers { 2 ); IEnumerable posts = thread.getPosts(pageOuter, context); + + int lastReadId = thread.getLastReadId(context.session); + thread.incrementViewsCounter(); + if((context.session != null) && (posts.Count() > 0)) { + thread.markAsRead( + context.session.account, + (from post in posts orderby post.id ascending select post).First(), + (from post in posts orderby post.id descending select post).First() + ); + } + return new XElement[] { new XElement("currentLocation", thread.exportToXmlSimpleWithParent(context)), new XElement("posts", - from post in posts select post.exportToXmlWithoutThread(context, true), + from post in posts select post.exportToXmlWithoutThread(context, true, new XElement("isUnread", (post.id > lastReadId).ToPlainString())), pageOuter.exportToXml(2, 5, 2) ) }; diff --git a/MySQLConnector/Connection.cs b/MySQLConnector/Connection.cs index fc2c5a0..195c711 100644 --- a/MySQLConnector/Connection.cs +++ b/MySQLConnector/Connection.cs @@ -32,46 +32,48 @@ namespace FLocal.MySQLConnector { } private List> _LoadByIds(DbCommand command, ITableSpec table, List ids, bool forUpdate) { - command.CommandType = System.Data.CommandType.Text; + lock(this) { + command.CommandType = System.Data.CommandType.Text; - ParamsHolder paramsHolder = new ParamsHolder(); - List placeholder = new List(); - foreach(string id in ids) { - placeholder.Add(this.traits.markParam(paramsHolder.Add(id))); - } + ParamsHolder paramsHolder = new ParamsHolder(); + List placeholder = new List(); + foreach(string id in ids) { + placeholder.Add(this.traits.markParam(paramsHolder.Add(id))); + } - command.CommandText = "SELECT * FROM " + table.compile(this.traits) + " WHERE " + table.getIdSpec().compile(this.traits) + " IN (" + string.Join(", ", placeholder.ToArray()) + ")" + (forUpdate ? " FOR UPDATE" : ""); - //command.Prepare(); - foreach(KeyValuePair kvp in paramsHolder.data) { - command.AddParameter(kvp.Key, kvp.Value); - } + command.CommandText = "SELECT * FROM " + table.compile(this.traits) + " WHERE " + table.getIdSpec().compile(this.traits) + " IN (" + string.Join(", ", placeholder.ToArray()) + ")" + (forUpdate ? " FOR UPDATE" : ""); + //command.Prepare(); + foreach(KeyValuePair kvp in paramsHolder.data) { + command.AddParameter(kvp.Key, kvp.Value); + } - Dictionary> rawResult = new Dictionary>(); - using(DbDataReader reader = command.ExecuteReader()) { - while(reader.Read()) { - Dictionary row = new Dictionary(); - for(int i=0; i> rawResult = new Dictionary>(); + using(DbDataReader reader = command.ExecuteReader()) { + while(reader.Read()) { + Dictionary row = new Dictionary(); + for(int i=0; i> result = new List>(); - foreach(string id in ids) { - if(rawResult.ContainsKey(id)) { - result.Add(rawResult[id]); + List> result = new List>(); + foreach(string id in ids) { + if(rawResult.ContainsKey(id)) { + result.Add(rawResult[id]); + } } + return result; } - return result; } public List> LoadByIds(ITableSpec table, List ids) { @@ -82,74 +84,77 @@ namespace FLocal.MySQLConnector { } } - public List LoadIdsByConditions(ITableSpec table, FLocal.Core.DB.conditions.AbstractCondition conditions, Diapasone diapasone, JoinSpec[] joins, SortSpec[] sorts, bool allowHugeLists) { - lock(this) { - using(DbCommand command = this.connection.CreateCommand()) { - - command.CommandType = System.Data.CommandType.Text; + private List _LoadIdsByConditions(DbCommand command, ITableSpec table, FLocal.Core.DB.conditions.AbstractCondition conditions, Diapasone diapasone, JoinSpec[] joins, SortSpec[] sorts, bool allowHugeLists) { + command.CommandType = System.Data.CommandType.Text; - var conditionsCompiled = ConditionCompiler.Compile(conditions, this.traits); - string queryConditions = ""; - if(conditionsCompiled.Key != "") queryConditions = "WHERE " + conditionsCompiled.Key; - ParamsHolder paramsHolder = conditionsCompiled.Value; + var conditionsCompiled = ConditionCompiler.Compile(conditions, this.traits); + string queryConditions = ""; + if(conditionsCompiled.Key != "") queryConditions = "WHERE " + conditionsCompiled.Key; + ParamsHolder paramsHolder = conditionsCompiled.Value; - string queryJoins = ""; - { - if(joins.Length > 0) { - throw new NotImplementedException(); - } - } + string queryJoins = ""; + { + if(joins.Length > 0) { + throw new NotImplementedException(); + } + } - string querySorts = ""; - 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 querySorts = ""; + 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 + " " + queryConditions; + string queryMain = "FROM " + table.compile(this.traits) + " " + queryJoins + " " + queryConditions; - foreach(KeyValuePair kvp in paramsHolder.data) { - command.AddParameter(kvp.Key, kvp.Value); - } + foreach(KeyValuePair kvp in paramsHolder.data) { + command.AddParameter(kvp.Key, kvp.Value); + } - command.CommandText = "SELECT COUNT(*) " + queryMain; - object rawCount; - //try { - rawCount = command.ExecuteScalar(); - //} catch(Npgsql.NpgsqlException e) { - //throw new FLocalException("Error while trying to execute " + command.CommandText + ": " + e.Message); - //} - long count = (long)rawCount; - if(count < 1) { - diapasone.total = 0; - return new List(); - } else { - diapasone.total = count; - if(diapasone.total > 1000 && diapasone.count < 0 && !allowHugeLists) { - throw new CriticalException("huge list"); - } - string queryLimits = ""; - if(diapasone.count >= 0) { - queryLimits = "LIMIT " + diapasone.count + " OFFSET " + diapasone.start; - } - command.CommandText = "SELECT " + table.compile(this.traits) + ".* " + queryMain + " " + querySorts + " " + queryLimits; + command.CommandText = "SELECT COUNT(*) " + queryMain; + object rawCount; + //try { + rawCount = command.ExecuteScalar(); + //} catch(Npgsql.NpgsqlException e) { + //throw new FLocalException("Error while trying to execute " + command.CommandText + ": " + e.Message); + //} + long count = (long)rawCount; + if(count < 1) { + diapasone.total = 0; + return new List(); + } else { + diapasone.total = count; + if(diapasone.total > 1000 && diapasone.count < 0 && !allowHugeLists) { + throw new CriticalException("huge list"); + } + string queryLimits = ""; + if(diapasone.count >= 0) { + queryLimits = "LIMIT " + diapasone.count + " OFFSET " + diapasone.start; + } + command.CommandText = "SELECT " + table.compile(this.traits) + ".* " + queryMain + " " + querySorts + " " + queryLimits; - List result = new List(); - using(DbDataReader reader = command.ExecuteReader()) { - while(reader.Read()) { - result.Add(reader.GetValue(0).ToString()); - } - } - return result; + List result = new List(); + using(DbDataReader reader = command.ExecuteReader()) { + while(reader.Read()) { + result.Add(reader.GetValue(0).ToString()); } } + return result; + } + } + + public List LoadIdsByConditions(ITableSpec table, FLocal.Core.DB.conditions.AbstractCondition conditions, Diapasone diapasone, JoinSpec[] joins, SortSpec[] sorts, bool allowHugeLists) { + lock(this) { + using(DbCommand command = this.connection.CreateCommand()) { + return this._LoadIdsByConditions(command, table, conditions, diapasone, joins, sorts, allowHugeLists); + } } } @@ -231,6 +236,16 @@ namespace FLocal.MySQLConnector { } } + public List LoadIdsByConditions(FLocal.Core.DB.Transaction _transaction, ITableSpec table, FLocal.Core.DB.conditions.AbstractCondition conditions, Diapasone diapasone, JoinSpec[] joins, SortSpec[] sorts, bool allowHugeLists) { + Transaction transaction = (Transaction)_transaction; + lock(this) { + using(DbCommand command = transaction.sqlconnection.CreateCommand()) { + command.Transaction = transaction.sqltransaction; + return this._LoadIdsByConditions(command, table, conditions, diapasone, joins, sorts, allowHugeLists); + } + } + } + public void update(FLocal.Core.DB.Transaction _transaction, ITableSpec table, string id, Dictionary data) { Transaction transaction = (Transaction)_transaction; lock(transaction) { diff --git a/templates/Full/elems/PostInfo.xslt b/templates/Full/elems/PostInfo.xslt index a23be01..b03b4e1 100644 --- a/templates/Full/elems/PostInfo.xslt +++ b/templates/Full/elems/PostInfo.xslt @@ -17,10 +17,21 @@ /Post// - + + + + /static/images/book-notread.gif + + + /static/images/book-read.gif + + + - new + + new + [ diff --git a/templates/Full/elems/ThreadInfo.xslt b/templates/Full/elems/ThreadInfo.xslt index 50c8718..57a6f57 100644 --- a/templates/Full/elems/ThreadInfo.xslt +++ b/templates/Full/elems/ThreadInfo.xslt @@ -15,7 +15,7 @@ * - + /static/images/book-notread.gif @@ -24,7 +24,15 @@ - /Thread// + + /Thread/ + + / + + p + + +