forked from halo-dev/halo
-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'upstream/main'
- Loading branch information
Showing
71 changed files
with
1,497 additions
and
882 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
186 changes: 186 additions & 0 deletions
186
application/src/main/java/run/halo/app/content/TagPostCountUpdater.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
package run.halo.app.content; | ||
|
||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; | ||
import static run.halo.app.extension.index.query.QueryFactory.and; | ||
import static run.halo.app.extension.index.query.QueryFactory.equal; | ||
import static run.halo.app.extension.index.query.QueryFactory.isNull; | ||
|
||
import com.google.common.collect.Sets; | ||
import java.time.Duration; | ||
import java.time.Instant; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.springframework.context.SmartLifecycle; | ||
import org.springframework.context.event.EventListener; | ||
import org.springframework.stereotype.Component; | ||
import run.halo.app.core.extension.content.Post; | ||
import run.halo.app.core.extension.content.Tag; | ||
import run.halo.app.core.extension.content.Tag.TagStatus; | ||
import run.halo.app.event.post.PostDeletedEvent; | ||
import run.halo.app.event.post.PostEvent; | ||
import run.halo.app.event.post.PostUpdatedEvent; | ||
import run.halo.app.extension.ExtensionClient; | ||
import run.halo.app.extension.ListOptions; | ||
import run.halo.app.extension.MetadataUtil; | ||
import run.halo.app.extension.PageRequestImpl; | ||
import run.halo.app.extension.controller.Controller; | ||
import run.halo.app.extension.controller.ControllerBuilder; | ||
import run.halo.app.extension.controller.DefaultController; | ||
import run.halo.app.extension.controller.DefaultQueue; | ||
import run.halo.app.extension.controller.Reconciler; | ||
import run.halo.app.extension.controller.RequestQueue; | ||
import run.halo.app.extension.router.selector.FieldSelector; | ||
import run.halo.app.extension.router.selector.LabelSelector; | ||
import run.halo.app.infra.utils.JsonUtils; | ||
|
||
/** | ||
* Update {@link TagStatus#postCount} when post related to tag is updated. | ||
* | ||
* @author guqing | ||
* @since 2.13.0 | ||
*/ | ||
@Component | ||
public class TagPostCountUpdater | ||
implements Reconciler<TagPostCountUpdater.PostRelatedTags>, SmartLifecycle { | ||
|
||
private final RequestQueue<PostRelatedTags> tagQueue; | ||
|
||
private final Controller postEventController; | ||
|
||
private final ExtensionClient client; | ||
|
||
private volatile boolean running = false; | ||
|
||
/** | ||
* Construct a {@link TagPostCountUpdater} with the given {@link ExtensionClient}. | ||
*/ | ||
public TagPostCountUpdater(ExtensionClient client) { | ||
this.client = client; | ||
|
||
this.tagQueue = new DefaultQueue<>(Instant::now); | ||
this.postEventController = this.setupWith(null); | ||
} | ||
|
||
@Override | ||
public Result reconcile(PostRelatedTags postRelatedTags) { | ||
for (var tag : postRelatedTags.tags()) { | ||
updateTagRelatedPostCount(tag); | ||
} | ||
|
||
// Update last associated tags when handled | ||
client.fetch(Post.class, postRelatedTags.postName()).ifPresent(post -> { | ||
var tags = defaultIfNull(post.getSpec().getTags(), List.<String>of()); | ||
var annotations = MetadataUtil.nullSafeAnnotations(post); | ||
var tagAnno = JsonUtils.objectToJson(tags); | ||
var oldTagAnno = annotations.get(Post.LAST_ASSOCIATED_TAGS_ANNO); | ||
|
||
if (!tagAnno.equals(oldTagAnno)) { | ||
annotations.put(Post.LAST_ASSOCIATED_TAGS_ANNO, tagAnno); | ||
client.update(post); | ||
} | ||
}); | ||
return Result.doNotRetry(); | ||
} | ||
|
||
|
||
@Override | ||
public Controller setupWith(ControllerBuilder builder) { | ||
return new DefaultController<>( | ||
this.getClass().getName(), | ||
this, | ||
tagQueue, | ||
null, | ||
Duration.ofMillis(100), | ||
Duration.ofMinutes(10) | ||
); | ||
} | ||
|
||
@Override | ||
public void start() { | ||
postEventController.start(); | ||
running = true; | ||
} | ||
|
||
@Override | ||
public void stop() { | ||
running = false; | ||
postEventController.dispose(); | ||
} | ||
|
||
@Override | ||
public boolean isRunning() { | ||
return running; | ||
} | ||
|
||
/** | ||
* Listen to post event to calculate post related to tag for updating. | ||
*/ | ||
@EventListener(PostEvent.class) | ||
public void onPostUpdated(PostEvent postEvent) { | ||
var postName = postEvent.getName(); | ||
if (postEvent instanceof PostUpdatedEvent) { | ||
var tagsToUpdate = calcTagsToUpdate(postEvent.getName()); | ||
tagQueue.addImmediately(new PostRelatedTags(postName, tagsToUpdate)); | ||
return; | ||
} | ||
|
||
if (postEvent instanceof PostDeletedEvent deletedEvent) { | ||
var tags = defaultIfNull(deletedEvent.getPost().getSpec().getTags(), | ||
List.<String>of()); | ||
tagQueue.addImmediately(new PostRelatedTags(postName, Sets.newHashSet(tags))); | ||
} | ||
} | ||
|
||
private Set<String> calcTagsToUpdate(String postName) { | ||
var post = client.fetch(Post.class, postName).orElseThrow(); | ||
var annotations = MetadataUtil.nullSafeAnnotations(post); | ||
var oldTags = Optional.ofNullable(annotations.get(Post.LAST_ASSOCIATED_TAGS_ANNO)) | ||
.filter(StringUtils::isNotBlank) | ||
.map(tagsJson -> JsonUtils.jsonToObject(tagsJson, String[].class)) | ||
.orElse(new String[0]); | ||
|
||
var tagsToUpdate = Sets.newHashSet(oldTags); | ||
var newTags = post.getSpec().getTags(); | ||
if (newTags != null) { | ||
tagsToUpdate.addAll(newTags); | ||
} | ||
return tagsToUpdate; | ||
} | ||
|
||
public record PostRelatedTags(String postName, Set<String> tags) { | ||
} | ||
|
||
private void updateTagRelatedPostCount(String tagName) { | ||
client.fetch(Tag.class, tagName).ifPresent(tag -> { | ||
var commonFieldQuery = and( | ||
equal("spec.tags", tag.getMetadata().getName()), | ||
isNull("metadata.deletionTimestamp") | ||
); | ||
// Update post count | ||
var allPostOptions = new ListOptions(); | ||
allPostOptions.setFieldSelector(FieldSelector.of(commonFieldQuery)); | ||
var result = client.listBy(Post.class, allPostOptions, PageRequestImpl.ofSize(1)); | ||
tag.getStatusOrDefault().setPostCount((int) result.getTotal()); | ||
|
||
// Update visible post count | ||
var publicPostOptions = new ListOptions(); | ||
publicPostOptions.setLabelSelector(LabelSelector.builder() | ||
.eq(Post.PUBLISHED_LABEL, "true") | ||
.build()); | ||
publicPostOptions.setFieldSelector(FieldSelector.of( | ||
and( | ||
commonFieldQuery, | ||
equal("spec.deleted", "false"), | ||
equal("spec.visible", Post.VisibleEnum.PUBLIC.name()) | ||
) | ||
)); | ||
var publicPosts = | ||
client.listBy(Post.class, publicPostOptions, PageRequestImpl.ofSize(1)); | ||
tag.getStatusOrDefault().setVisiblePostCount((int) publicPosts.getTotal()); | ||
|
||
client.update(tag); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.