Board ReadMarkers implemented; fixes in Thread ReadMarkers

main
Inga 🏳‍🌈 15 years ago
parent a314b252cf
commit 406cdf2076
  1. 2
      Builder/IISMainHandler/build.txt
  2. 2
      Builder/IISUploadHandler/build.txt
  3. 12
      Common/SqlObject.cs
  4. 7
      Common/UserContext.cs
  5. 1
      Common/actions/ChangeSet.cs
  6. 91
      Common/dataobjects/Board.cs
  7. 27
      Common/dataobjects/Thread.cs
  8. 9
      IISMainHandler/WebContext.cs
  9. 13
      IISMainHandler/handlers/BoardHandler.cs
  10. 17
      IISMainHandler/handlers/PostHandler.cs
  11. 21
      IISMainHandler/handlers/ThreadHandler.cs
  12. 2
      templates/Full/elems/Main.xslt

@ -99,7 +99,7 @@ namespace FLocal.Common {
} }
abstract public class SqlObject<T> : SqlObject<int, T> where T : SqlObject<T>, new() { abstract public class SqlObject<T> : SqlObject<int, T>, IComparable<T> where T : SqlObject<T>, new() {
public static List<T> LoadByIds(IEnumerable<int> ids) { public static List<T> LoadByIds(IEnumerable<int> ids) {
@ -135,6 +135,16 @@ namespace FLocal.Common {
return res; return res;
} }
int IComparable<T>.CompareTo(T other) {
if(other.id > this.id) {
return -1;
} else if(other.id == this.id) {
return 0;
} else {
return 1;
}
}
} }
} }

@ -25,6 +25,13 @@ namespace FLocal.Common {
abstract public XElement formatTotalPosts(long posts); abstract public XElement formatTotalPosts(long posts);
/// <summary>
/// May be null
/// </summary>
abstract public dataobjects.Account account {
get;
}
} }
public static class UserContext_Extensions { public static class UserContext_Extensions {

@ -24,6 +24,7 @@ namespace FLocal.Common.actions {
dataobjects.Board.TableSpec.TABLE, dataobjects.Board.TableSpec.TABLE,
dataobjects.Thread.TableSpec.TABLE, dataobjects.Thread.TableSpec.TABLE,
dataobjects.Post.TableSpec.TABLE, dataobjects.Post.TableSpec.TABLE,
dataobjects.Board.ReadMarkerTableSpec.TABLE,
dataobjects.Thread.ReadMarkerTableSpec.TABLE, dataobjects.Thread.ReadMarkerTableSpec.TABLE,
dataobjects.Session.TableSpec.TABLE, dataobjects.Session.TableSpec.TABLE,
} }

@ -6,6 +6,7 @@ using System.Xml.Linq;
using FLocal.Core; using FLocal.Core;
using FLocal.Core.DB; using FLocal.Core.DB;
using FLocal.Core.DB.conditions; using FLocal.Core.DB.conditions;
using FLocal.Common.actions;
namespace FLocal.Common.dataobjects { namespace FLocal.Common.dataobjects {
public class Board : SqlObject<Board> { public class Board : SqlObject<Board> {
@ -27,6 +28,18 @@ namespace FLocal.Common.dataobjects {
public void refreshSqlObject(int id) { Refresh(id); } public void refreshSqlObject(int id) { Refresh(id); }
} }
public class ReadMarkerTableSpec : ISqlObjectTableSpec {
public const string TABLE = "Boards_ReadMarkers";
public const string FIELD_ID = "Id";
public const string FIELD_BOARDID = "BoardId";
public const string FIELD_ACCOUNTID = "AccountId";
public const string FIELD_LASTREADDATE = "LastReadDate";
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; } } protected override ISqlObjectTableSpec table { get { return TableSpec.instance; } }
private int _sortOrder; private int _sortOrder;
@ -149,8 +162,12 @@ namespace FLocal.Common.dataobjects {
Cache<IEnumerable<int>>.instance.delete(this.subBoards_Locker); Cache<IEnumerable<int>>.instance.delete(this.subBoards_Locker);
} }
private bool hasNewPosts() { public bool hasNewPosts(Account account) {
return Core.Util.RandomInt(0, 1000) < 500; if(!this.lastPostId.HasValue) {
return false;
} else {
return this.getLastReadDate(account) < this.lastPost.postDate;
}
} }
private XElement exportLastPostInfo(UserContext context) { private XElement exportLastPostInfo(UserContext context) {
@ -170,7 +187,7 @@ namespace FLocal.Common.dataobjects {
); );
} }
public XElement exportToXml(UserContext context, bool includeSubBoards) { public XElement exportToXml(UserContext context, bool includeSubBoards, params XElement[] additional) {
XElement result = new XElement("board", XElement result = new XElement("board",
new XElement("id", this.id), new XElement("id", this.id),
new XElement("sortOrder", this.sortOrder), new XElement("sortOrder", this.sortOrder),
@ -179,16 +196,23 @@ namespace FLocal.Common.dataobjects {
new XElement("totalThreads", this.totalThreads), new XElement("totalThreads", this.totalThreads),
new XElement("name", this.name), new XElement("name", this.name),
new XElement("description", this.description), new XElement("description", this.description),
new XElement("hasNewPosts", this.hasNewPosts().ToPlainString()),
new XElement("lastPostInfo", this.exportLastPostInfo(context)) new XElement("lastPostInfo", this.exportLastPostInfo(context))
); );
if(context.account != null) {
result.Add(new XElement("hasNewPosts", this.hasNewPosts(context.account).ToPlainString()));
}
if(includeSubBoards) { if(includeSubBoards) {
result.Add(new XElement("subBoards", result.Add(new XElement("subBoards",
from board in this.subBoards select board.exportToXml(context, false) from board in this.subBoards select board.exportToXml(context, false)
)); ));
} }
if(additional.Length > 0) {
result.Add(additional);
}
return result; return result;
} }
@ -225,5 +249,64 @@ namespace FLocal.Common.dataobjects {
); );
} }
public DateTime getLastReadDate(Account account) {
List<string> stringIds = Config.instance.mainConnection.LoadIdsByConditions(
ReadMarkerTableSpec.instance,
new ComplexCondition(
ConditionsJoinType.AND,
new ComparisonCondition(
ReadMarkerTableSpec.instance.getColumnSpec(ReadMarkerTableSpec.FIELD_BOARDID),
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 new DateTime(0);
}
Dictionary<string, string> data = Config.instance.mainConnection.LoadById(ReadMarkerTableSpec.instance, stringIds[0]);
return Util.ParseDateTimeFromTimestamp(data[ReadMarkerTableSpec.FIELD_LASTREADDATE]).Value;
}
public void markAsRead(Account account) {
if(this.lastPostId.HasValue) {
ChangeSetUtil.ApplyChanges(
new InsertOrUpdateChange(
ReadMarkerTableSpec.instance,
new Dictionary<string,AbstractFieldValue> {
{ ReadMarkerTableSpec.FIELD_BOARDID, new ScalarFieldValue(this.id.ToString()) },
{ ReadMarkerTableSpec.FIELD_ACCOUNTID, new ScalarFieldValue(account.id.ToString()) },
{ ReadMarkerTableSpec.FIELD_LASTREADDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) },
},
new Dictionary<string,AbstractFieldValue> {
{ ReadMarkerTableSpec.FIELD_LASTREADDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) },
},
new ComplexCondition(
ConditionsJoinType.AND,
new ComparisonCondition(
ReadMarkerTableSpec.instance.getColumnSpec(ReadMarkerTableSpec.FIELD_BOARDID),
ComparisonType.EQUAL,
this.id.ToString()
),
new ComparisonCondition(
ReadMarkerTableSpec.instance.getColumnSpec(ReadMarkerTableSpec.FIELD_ACCOUNTID),
ComparisonType.EQUAL,
account.id.ToString()
)
)
)
);
}
}
} }
} }

@ -152,10 +152,6 @@ namespace FLocal.Common.dataobjects {
this._totalViews = int.Parse(data[TableSpec.FIELD_TOTALVIEWS]); this._totalViews = int.Parse(data[TableSpec.FIELD_TOTALVIEWS]);
} }
private bool hasNewPosts() {
return Core.Util.RandomInt(0, 1000) < 500;
}
public XElement exportToXmlSimpleWithParent(UserContext context) { public XElement exportToXmlSimpleWithParent(UserContext context) {
return new XElement("thread", return new XElement("thread",
new XElement("id", this.id), new XElement("id", this.id),
@ -182,6 +178,9 @@ namespace FLocal.Common.dataobjects {
if(includeFirstPost) { if(includeFirstPost) {
result.Add(new XElement("firstPost", this.firstPost.exportToXmlWithoutThread(context, false))); result.Add(new XElement("firstPost", this.firstPost.exportToXmlWithoutThread(context, false)));
} }
if(context.account != null) {
result.Add(new XElement("afterLastRead", this.getLastReadId(context.account) + 1));
}
if(additional.Length > 0) { if(additional.Length > 0) {
result.Add(additional); result.Add(additional);
} }
@ -224,7 +223,7 @@ namespace FLocal.Common.dataobjects {
}); });
} }
private Post getLastRead(Account account) { public int getLastReadId(Account account) {
List<string> stringIds = Config.instance.mainConnection.LoadIdsByConditions( List<string> stringIds = Config.instance.mainConnection.LoadIdsByConditions(
ReadMarkerTableSpec.instance, ReadMarkerTableSpec.instance,
new ComplexCondition( new ComplexCondition(
@ -246,24 +245,13 @@ namespace FLocal.Common.dataobjects {
throw new CriticalException("more than one row"); throw new CriticalException("more than one row");
} }
if(stringIds.Count < 1) { if(stringIds.Count < 1) {
return null; return 0;
} }
Dictionary<string, string> data = Config.instance.mainConnection.LoadById(ReadMarkerTableSpec.instance, stringIds[0]); Dictionary<string, string> data = Config.instance.mainConnection.LoadById(ReadMarkerTableSpec.instance, stringIds[0]);
if((data[ReadMarkerTableSpec.FIELD_POSTID] == "") || (data[ReadMarkerTableSpec.FIELD_POSTID] == null)) { 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; return 0;
} }
Post post = this.getLastRead(session.account); return int.Parse(data[ReadMarkerTableSpec.FIELD_POSTID]);
if(post == null) {
return 0;
}
return post.id;
} }
public void markAsRead(Account account, Post minPost, Post maxPost) { public void markAsRead(Account account, Post minPost, Post maxPost) {
@ -282,7 +270,7 @@ namespace FLocal.Common.dataobjects {
{ {
ReadMarkerTableSpec.FIELD_POSTID, ReadMarkerTableSpec.FIELD_POSTID,
new ScalarFieldValue( new ScalarFieldValue(
(minPost.id < this.firstPostId) (minPost.id <= this.firstPostId)
? ?
maxPost.id.ToString() maxPost.id.ToString()
: :
@ -330,7 +318,6 @@ namespace FLocal.Common.dataobjects {
} }
) )
} }
}, },
new ComplexCondition( new ComplexCondition(
ConditionsJoinType.AND, ConditionsJoinType.AND,

@ -62,6 +62,15 @@ namespace FLocal.IISHandler {
public Session session; public Session session;
public override Account account {
get {
if(this.session == null) {
return null;
}
return this.session.account;
}
}
public WebContext(HttpContext httpcontext) { public WebContext(HttpContext httpcontext) {
this.httpcontext = httpcontext; this.httpcontext = httpcontext;
this.requestTime = DateTime.Now; this.requestTime = DateTime.Now;

@ -6,6 +6,7 @@ using System.Web;
using System.Xml.Linq; using System.Xml.Linq;
using FLocal.Common; using FLocal.Common;
using FLocal.Common.dataobjects; using FLocal.Common.dataobjects;
using FLocal.Core;
using FLocal.Core.DB; using FLocal.Core.DB;
namespace FLocal.IISHandler.handlers { namespace FLocal.IISHandler.handlers {
@ -22,14 +23,22 @@ namespace FLocal.IISHandler.handlers {
Board board = Board.LoadById(int.Parse(context.requestParts[1])); Board board = Board.LoadById(int.Parse(context.requestParts[1]));
PageOuter pageOuter = PageOuter.createFromGet(context.requestParts, context.userSettings.threadsPerPage, 2); PageOuter pageOuter = PageOuter.createFromGet(context.requestParts, context.userSettings.threadsPerPage, 2);
IEnumerable<Thread> threads = board.getThreads(pageOuter, context); IEnumerable<Thread> threads = board.getThreads(pageOuter, context);
return new XElement[] { XElement[] result = new XElement[] {
new XElement("currentLocation", board.exportToXmlSimpleWithParent(context)), new XElement("currentLocation", board.exportToXmlSimpleWithParent(context)),
new XElement("boards", from subBoard in board.subBoards select subBoard.exportToXml(context, true)), new XElement("boards", from subBoard in board.subBoards select subBoard.exportToXml(context, true)),
new XElement("threads", new XElement("threads",
(from thread in threads select thread.exportToXml(context, false, new XElement("afterLastRead", thread.getLastReadId(context.session) + 1))).addNumbers(), (from thread in threads select thread.exportToXml(context, false)).addNumbers(),
pageOuter.exportToXml(1, 5, 1) pageOuter.exportToXml(1, 5, 1)
) )
}; };
if(context.session != null) {
if(pageOuter.start == 0) {
board.markAsRead(context.session.account);
}
}
return result;
} }
} }

@ -21,18 +21,23 @@ namespace FLocal.IISHandler.handlers {
override protected XElement[] getSpecificData(WebContext context) { override protected XElement[] getSpecificData(WebContext context) {
Post post = Post.LoadById(int.Parse(context.requestParts[1])); Post post = Post.LoadById(int.Parse(context.requestParts[1]));
int lastReadId = post.thread.getLastReadId(context.session); int lastReadId = 0;
post.thread.incrementViewsCounter();
if(context.session != null) { if(context.session != null) {
post.thread.markAsRead(context.session.account, post, post); lastReadId = post.thread.getLastReadId(context.session.account);
} }
return new XElement[] { XElement[] result = new XElement[] {
new XElement("currentLocation", post.exportToXmlSimpleWithParent(context)), new XElement("currentLocation", post.exportToXmlSimpleWithParent(context)),
new XElement("posts", post.exportToXmlWithoutThread(context, true, new XElement("isUnread", (post.id > lastReadId).ToPlainString()))) new XElement("posts", post.exportToXmlWithoutThread(context, true, new XElement("isUnread", (post.id > lastReadId).ToPlainString())))
}; };
}
post.thread.incrementViewsCounter();
if(context.session != null) {
post.thread.markAsRead(context.session.account, post, post);
}
return result;
}
} }

@ -51,24 +51,25 @@ namespace FLocal.IISHandler.handlers {
); );
IEnumerable<Post> posts = thread.getPosts(pageOuter, context); IEnumerable<Post> posts = thread.getPosts(pageOuter, context);
int lastReadId = thread.getLastReadId(context.session); int lastReadId = 0;
if(context.session != null) {
thread.incrementViewsCounter(); lastReadId = thread.getLastReadId(context.session.account);
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[] { XElement[] result = new XElement[] {
new XElement("currentLocation", thread.exportToXmlSimpleWithParent(context)), new XElement("currentLocation", thread.exportToXmlSimpleWithParent(context)),
new XElement("posts", new XElement("posts",
from post in posts select post.exportToXmlWithoutThread(context, true, new XElement("isUnread", (post.id > lastReadId).ToPlainString())), from post in posts select post.exportToXmlWithoutThread(context, true, new XElement("isUnread", (post.id > lastReadId).ToPlainString())),
pageOuter.exportToXml(2, 5, 2) pageOuter.exportToXml(2, 5, 2)
) )
}; };
thread.incrementViewsCounter();
if((context.session != null) && (posts.Count() > 0)) {
thread.markAsRead(context.session.account, posts.Min(), posts.Max());
}
return result;
} }
} }

@ -13,9 +13,11 @@
<body> <body>
<xsl:call-template name="header"/> <xsl:call-template name="header"/>
<xsl:call-template name="specific"/> <xsl:call-template name="specific"/>
<xsl:text disable-output-escaping="yes"><![CDATA[<!--]]></xsl:text>
<br /> <br />
<xsl:text>Data used for authoring this XHTML document:</xsl:text> <xsl:text>Data used for authoring this XHTML document:</xsl:text>
<xmp><xsl:copy-of select="/"/></xmp> <xmp><xsl:copy-of select="/"/></xmp>
<xsl:text disable-output-escaping="yes"><![CDATA[-->]]></xsl:text>
</body> </body>
</html> </html>
</xsl:template> </xsl:template>

Loading…
Cancel
Save