Fossil SCM

Initial (untested) code for creating the control artifact for closing and re-opening forum threads. Extend test-forumthread's tree view to show thread closure.

stephan 2023-02-21 10:30 forumpost-locking
Commit 32fc62e68160783ab85ed1ceeb0ab3d211028aeb6f8d3c9e0959821321f89bd0
--- src/default.css
+++ src/default.css
@@ -907,10 +907,13 @@
907907
div.forumClosed > *:first-child::before {
908908
content: "[CLOSED] ";
909909
color: red;
910910
opacity: 0.7;
911911
}
912
+/*div.forumClosed > div.forumPostBody {
913
+ filter: blur(5px);
914
+}*/
912915
.forum div > form {
913916
margin: 0.5em 0;
914917
}
915918
.forum-post-collapser {
916919
/* Common style for the bottom-of-post and right-of-post
917920
--- src/default.css
+++ src/default.css
@@ -907,10 +907,13 @@
907 div.forumClosed > *:first-child::before {
908 content: "[CLOSED] ";
909 color: red;
910 opacity: 0.7;
911 }
 
 
 
912 .forum div > form {
913 margin: 0.5em 0;
914 }
915 .forum-post-collapser {
916 /* Common style for the bottom-of-post and right-of-post
917
--- src/default.css
+++ src/default.css
@@ -907,10 +907,13 @@
907 div.forumClosed > *:first-child::before {
908 content: "[CLOSED] ";
909 color: red;
910 opacity: 0.7;
911 }
912 /*div.forumClosed > div.forumPostBody {
913 filter: blur(5px);
914 }*/
915 .forum div > form {
916 margin: 0.5em 0;
917 }
918 .forum-post-collapser {
919 /* Common style for the bottom-of-post and right-of-post
920
+97 -3
--- src/forum.c
+++ src/forum.c
@@ -90,11 +90,11 @@
9090
** no active tag is found.
9191
**
9292
** If bCheckParents is true then p's thread parents are checked
9393
** (recursively) for closure, else only p is checked.
9494
*/
95
-int forum_post_is_closed(ForumPost *p, int bCheckParents){
95
+static int forum_post_is_closed(ForumPost *p, int bCheckParents){
9696
if( !p ) return 0;
9797
if( p->pEditTail ) p = p->pEditTail;
9898
if( p->iClosed || !bCheckParents ) return p->iClosed;
9999
else if( p->pIrt ){
100100
return forum_post_is_closed(p->pIrt->pEditTail
@@ -142,10 +142,100 @@
142142
if( rid ){
143143
rc = forum_rid_is_closed(rid, 1);
144144
}
145145
return rc>0 ? -rc : rc;
146146
}
147
+
148
+/*
149
+** UNTESTED!
150
+**
151
+** Closes or re-opens the given forum RID via addition of a new
152
+** control artifact into the repository.
153
+**
154
+** If doClose is true then a propagating "closed" tag is added, except
155
+** as noted below, with the given optional zReason string as the tag's
156
+** value. If doClose is false then any active "closed" tag on frid is
157
+** cancelled, except as noted below. zReason is ignored if doClose is
158
+** false or if zReason is NULL or starts with a NUL byte.
159
+**
160
+** This function only adds a "closed" tag to frid if
161
+** forum_rid_is_closed() indicates that frid is not closed. If a
162
+** parent post is already closed, no tag is added. Similarly, it will
163
+** only remove a "closed" tag from a post which has its own "closed"
164
+** tag, and will not remove an inherited one from a parent post.
165
+**
166
+** If doClose is true and frid is closed (directly or inherited), this
167
+** is a no-op. Likewise, if doClose is false and frid itself is not
168
+** closed (not accounting for an inherited closed tag), this is a
169
+** no-op.
170
+**
171
+** Returns true if it actually creates a new tag, else false. Fails
172
+** fatally on error. If it returns true then any ForumPost::iClosed
173
+** values from previously loaded posts are invalidated if they refer
174
+** to the amended post or a response to it.
175
+**
176
+** Sidebars:
177
+**
178
+** - Unless the caller has a transaction open, via
179
+** db_begin_transaction(), there is a very tiny race condition
180
+** window during which the caller's idea of whether or not the forum
181
+** post is closed may differ from the current repository state.
182
+**
183
+** - This routine assumes that frid really does refer to a forum post.
184
+**
185
+** - This routine assumes that frid is not private or pending
186
+** moderation.
187
+**
188
+** - Closure of a forum post requires a propagating "closed" tag to
189
+** account for how edits of posts are handled. This differs from
190
+** closure of a branch, where a non-propagating tag is used.
191
+*/
192
+/*static*/ int forumpost_close(int frid, int doClose, const char *zReason){
193
+ Blob artifact = BLOB_INITIALIZER; /* Output artifact */
194
+ Blob cksum = BLOB_INITIALIZER; /* Z-card */
195
+ int iClosed; /* true if frid is closed */
196
+ int trid; /* RID of new control artifact */
197
+
198
+ db_begin_transaction();
199
+ iClosed = forum_rid_is_closed(frid, 1);
200
+ if( (iClosed && doClose
201
+ /* Already closed, noting that in the case of (iClosed<0), it's
202
+ ** actually a parent which is closed. */)
203
+ || (iClosed<=0 && !doClose
204
+ /* This entry is not closed, but a parent post may be. */) ){
205
+ db_end_transaction(0);
206
+ return 0;
207
+ }
208
+ if( doClose==0 || (zReason && !zReason[0]) ){
209
+ zReason = 0;
210
+ }
211
+ blob_appendf(&artifact, "D %z\n", date_in_standard_format( "now" ));
212
+ blob_appendf(&artifact,
213
+ "T %cclosed %z%s%F\n",
214
+ doClose ? '*' : '-', rid_to_uuid(frid),
215
+ zReason ? " " : "", zReason ? zReason : "");
216
+ blob_appendf(&artifact, "U %F\n", login_name());
217
+ md5sum_blob(&artifact, &cksum);
218
+ blob_appendf(&artifact, "Z %b\n", &cksum);
219
+ blob_reset(&cksum);
220
+ trid = content_put_ex(&artifact, 0, 0, 0, 0);
221
+ if( trid==0 ){
222
+ fossil_fatal("Error saving tag artifact: %s", g.zErrMsg);
223
+ }
224
+ if( manifest_crosslink(trid, &artifact,
225
+ MC_NONE /*MC_PERMIT_HOOKS?*/)==0 ){
226
+ fossil_fatal("%s", g.zErrMsg);
227
+ }
228
+ assert( blob_is_reset(&artifact) );
229
+ db_add_unsent(trid);
230
+ /* Potential TODO: if (iClosed>0) then we could find the initial tag
231
+ ** artifact and content_deltify(thatRid,&trid,1,0). Given the tiny
232
+ ** size of these artifacts, however, that would save little space,
233
+ ** if any. */
234
+ db_end_transaction(0);
235
+ return 1;
236
+}
147237
148238
/*
149239
** If iClosed is true and the current user has admin privileges, this
150240
** renders either a checkbox to unlock forum post fpid (if iClosed>0)
151241
** or a SPAN.warning element that the given post inherits the CLOSED
@@ -407,14 +497,18 @@
407497
}
408498
fossil_print("\nDisplay\n");
409499
for(p=pThread->pDisplay; p; p=p->pDisplay){
410500
fossil_print("%*s", (p->nIndent-1)*3, "");
411501
if( p->pEditTail ){
412
- fossil_print("%d->%d\n", p->fpid, p->pEditTail->fpid);
502
+ fossil_print("%d->%d", p->fpid, p->pEditTail->fpid);
413503
}else{
414
- fossil_print("%d\n", p->fpid);
504
+ fossil_print("%d", p->fpid);
505
+ }
506
+ if( p->iClosed ){
507
+ fossil_print(" [closed%s]", p->iClosed<0 ? " via parent" : "");
415508
}
509
+ fossil_print("\n");
416510
}
417511
forumthread_delete(pThread);
418512
}
419513
420514
/*
421515
--- src/forum.c
+++ src/forum.c
@@ -90,11 +90,11 @@
90 ** no active tag is found.
91 **
92 ** If bCheckParents is true then p's thread parents are checked
93 ** (recursively) for closure, else only p is checked.
94 */
95 int forum_post_is_closed(ForumPost *p, int bCheckParents){
96 if( !p ) return 0;
97 if( p->pEditTail ) p = p->pEditTail;
98 if( p->iClosed || !bCheckParents ) return p->iClosed;
99 else if( p->pIrt ){
100 return forum_post_is_closed(p->pIrt->pEditTail
@@ -142,10 +142,100 @@
142 if( rid ){
143 rc = forum_rid_is_closed(rid, 1);
144 }
145 return rc>0 ? -rc : rc;
146 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
148 /*
149 ** If iClosed is true and the current user has admin privileges, this
150 ** renders either a checkbox to unlock forum post fpid (if iClosed>0)
151 ** or a SPAN.warning element that the given post inherits the CLOSED
@@ -407,14 +497,18 @@
407 }
408 fossil_print("\nDisplay\n");
409 for(p=pThread->pDisplay; p; p=p->pDisplay){
410 fossil_print("%*s", (p->nIndent-1)*3, "");
411 if( p->pEditTail ){
412 fossil_print("%d->%d\n", p->fpid, p->pEditTail->fpid);
413 }else{
414 fossil_print("%d\n", p->fpid);
 
 
 
415 }
 
416 }
417 forumthread_delete(pThread);
418 }
419
420 /*
421
--- src/forum.c
+++ src/forum.c
@@ -90,11 +90,11 @@
90 ** no active tag is found.
91 **
92 ** If bCheckParents is true then p's thread parents are checked
93 ** (recursively) for closure, else only p is checked.
94 */
95 static int forum_post_is_closed(ForumPost *p, int bCheckParents){
96 if( !p ) return 0;
97 if( p->pEditTail ) p = p->pEditTail;
98 if( p->iClosed || !bCheckParents ) return p->iClosed;
99 else if( p->pIrt ){
100 return forum_post_is_closed(p->pIrt->pEditTail
@@ -142,10 +142,100 @@
142 if( rid ){
143 rc = forum_rid_is_closed(rid, 1);
144 }
145 return rc>0 ? -rc : rc;
146 }
147
148 /*
149 ** UNTESTED!
150 **
151 ** Closes or re-opens the given forum RID via addition of a new
152 ** control artifact into the repository.
153 **
154 ** If doClose is true then a propagating "closed" tag is added, except
155 ** as noted below, with the given optional zReason string as the tag's
156 ** value. If doClose is false then any active "closed" tag on frid is
157 ** cancelled, except as noted below. zReason is ignored if doClose is
158 ** false or if zReason is NULL or starts with a NUL byte.
159 **
160 ** This function only adds a "closed" tag to frid if
161 ** forum_rid_is_closed() indicates that frid is not closed. If a
162 ** parent post is already closed, no tag is added. Similarly, it will
163 ** only remove a "closed" tag from a post which has its own "closed"
164 ** tag, and will not remove an inherited one from a parent post.
165 **
166 ** If doClose is true and frid is closed (directly or inherited), this
167 ** is a no-op. Likewise, if doClose is false and frid itself is not
168 ** closed (not accounting for an inherited closed tag), this is a
169 ** no-op.
170 **
171 ** Returns true if it actually creates a new tag, else false. Fails
172 ** fatally on error. If it returns true then any ForumPost::iClosed
173 ** values from previously loaded posts are invalidated if they refer
174 ** to the amended post or a response to it.
175 **
176 ** Sidebars:
177 **
178 ** - Unless the caller has a transaction open, via
179 ** db_begin_transaction(), there is a very tiny race condition
180 ** window during which the caller's idea of whether or not the forum
181 ** post is closed may differ from the current repository state.
182 **
183 ** - This routine assumes that frid really does refer to a forum post.
184 **
185 ** - This routine assumes that frid is not private or pending
186 ** moderation.
187 **
188 ** - Closure of a forum post requires a propagating "closed" tag to
189 ** account for how edits of posts are handled. This differs from
190 ** closure of a branch, where a non-propagating tag is used.
191 */
192 /*static*/ int forumpost_close(int frid, int doClose, const char *zReason){
193 Blob artifact = BLOB_INITIALIZER; /* Output artifact */
194 Blob cksum = BLOB_INITIALIZER; /* Z-card */
195 int iClosed; /* true if frid is closed */
196 int trid; /* RID of new control artifact */
197
198 db_begin_transaction();
199 iClosed = forum_rid_is_closed(frid, 1);
200 if( (iClosed && doClose
201 /* Already closed, noting that in the case of (iClosed<0), it's
202 ** actually a parent which is closed. */)
203 || (iClosed<=0 && !doClose
204 /* This entry is not closed, but a parent post may be. */) ){
205 db_end_transaction(0);
206 return 0;
207 }
208 if( doClose==0 || (zReason && !zReason[0]) ){
209 zReason = 0;
210 }
211 blob_appendf(&artifact, "D %z\n", date_in_standard_format( "now" ));
212 blob_appendf(&artifact,
213 "T %cclosed %z%s%F\n",
214 doClose ? '*' : '-', rid_to_uuid(frid),
215 zReason ? " " : "", zReason ? zReason : "");
216 blob_appendf(&artifact, "U %F\n", login_name());
217 md5sum_blob(&artifact, &cksum);
218 blob_appendf(&artifact, "Z %b\n", &cksum);
219 blob_reset(&cksum);
220 trid = content_put_ex(&artifact, 0, 0, 0, 0);
221 if( trid==0 ){
222 fossil_fatal("Error saving tag artifact: %s", g.zErrMsg);
223 }
224 if( manifest_crosslink(trid, &artifact,
225 MC_NONE /*MC_PERMIT_HOOKS?*/)==0 ){
226 fossil_fatal("%s", g.zErrMsg);
227 }
228 assert( blob_is_reset(&artifact) );
229 db_add_unsent(trid);
230 /* Potential TODO: if (iClosed>0) then we could find the initial tag
231 ** artifact and content_deltify(thatRid,&trid,1,0). Given the tiny
232 ** size of these artifacts, however, that would save little space,
233 ** if any. */
234 db_end_transaction(0);
235 return 1;
236 }
237
238 /*
239 ** If iClosed is true and the current user has admin privileges, this
240 ** renders either a checkbox to unlock forum post fpid (if iClosed>0)
241 ** or a SPAN.warning element that the given post inherits the CLOSED
@@ -407,14 +497,18 @@
497 }
498 fossil_print("\nDisplay\n");
499 for(p=pThread->pDisplay; p; p=p->pDisplay){
500 fossil_print("%*s", (p->nIndent-1)*3, "");
501 if( p->pEditTail ){
502 fossil_print("%d->%d", p->fpid, p->pEditTail->fpid);
503 }else{
504 fossil_print("%d", p->fpid);
505 }
506 if( p->iClosed ){
507 fossil_print(" [closed%s]", p->iClosed<0 ? " via parent" : "");
508 }
509 fossil_print("\n");
510 }
511 forumthread_delete(pThread);
512 }
513
514 /*
515
+4
--- src/tag.c
+++ src/tag.c
@@ -910,10 +910,14 @@
910910
/*
911911
** Returns tagxref.rowid if the given blob.rid has a tagxref.rid entry
912912
** of an active (non-cancelled) tag matching the given rid and tag
913913
** name string, else returns 0. Note that this function does not
914914
** distinguish between a non-existent tag and a cancelled tag.
915
+**
916
+** Design note: the return value is the tagxref.rowid because that
917
+** gives us an easy way to fetch the value of the tag later on, if
918
+** needed.
915919
*/
916920
int rid_has_active_tag_name(int rid, const char *zTagName){
917921
static Stmt q = empty_Stmt_m;
918922
int rc;
919923
920924
--- src/tag.c
+++ src/tag.c
@@ -910,10 +910,14 @@
910 /*
911 ** Returns tagxref.rowid if the given blob.rid has a tagxref.rid entry
912 ** of an active (non-cancelled) tag matching the given rid and tag
913 ** name string, else returns 0. Note that this function does not
914 ** distinguish between a non-existent tag and a cancelled tag.
 
 
 
 
915 */
916 int rid_has_active_tag_name(int rid, const char *zTagName){
917 static Stmt q = empty_Stmt_m;
918 int rc;
919
920
--- src/tag.c
+++ src/tag.c
@@ -910,10 +910,14 @@
910 /*
911 ** Returns tagxref.rowid if the given blob.rid has a tagxref.rid entry
912 ** of an active (non-cancelled) tag matching the given rid and tag
913 ** name string, else returns 0. Note that this function does not
914 ** distinguish between a non-existent tag and a cancelled tag.
915 **
916 ** Design note: the return value is the tagxref.rowid because that
917 ** gives us an easy way to fetch the value of the tag later on, if
918 ** needed.
919 */
920 int rid_has_active_tag_name(int rid, const char *zTagName){
921 static Stmt q = empty_Stmt_m;
922 int rc;
923
924

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button