Skip to content

Commit

Permalink
Merge branch 'main' into refactor/indexed-reply
Browse files Browse the repository at this point in the history
  • Loading branch information
guqing committed Mar 12, 2024
2 parents 359c07b + ed23914 commit 77c5bcf
Show file tree
Hide file tree
Showing 15 changed files with 309 additions and 142 deletions.
Expand Up @@ -41,7 +41,8 @@ public class Post extends AbstractExtension {
public static final String CATEGORIES_ANNO = "content.halo.run/categories";
public static final String LAST_RELEASED_SNAPSHOT_ANNO =
"content.halo.run/last-released-snapshot";
public static final String TAGS_ANNO = "content.halo.run/tags";
public static final String LAST_ASSOCIATED_TAGS_ANNO = "content.halo.run/last-associated-tags";

public static final String DELETED_LABEL = "content.halo.run/deleted";
public static final String PUBLISHED_LABEL = "content.halo.run/published";
public static final String OWNER_LABEL = "content.halo.run/owner";
Expand Down
Expand Up @@ -27,6 +27,8 @@ public class Tag extends AbstractExtension {

public static final GroupVersionKind GVK = GroupVersionKind.fromExtension(Tag.class);

public static final String REQUIRE_SYNC_ON_STARTUP_INDEX_NAME = "requireSyncOnStartup";

@Schema(requiredMode = REQUIRED)
private TagSpec spec;

Expand Down Expand Up @@ -77,5 +79,7 @@ public static class TagStatus {
public Integer visiblePostCount;

public Integer postCount;

private Long observedVersion;
}
}
@@ -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);
});
}
}
Expand Up @@ -33,6 +33,7 @@
import run.halo.app.core.extension.content.Post.VisibleEnum;
import run.halo.app.core.extension.content.Snapshot;
import run.halo.app.core.extension.notification.Subscription;
import run.halo.app.event.post.PostDeletedEvent;
import run.halo.app.event.post.PostPublishedEvent;
import run.halo.app.event.post.PostUnpublishedEvent;
import run.halo.app.event.post.PostUpdatedEvent;
Expand Down Expand Up @@ -86,6 +87,7 @@ public Result reconcile(Request request) {
if (ExtensionOperator.isDeleted(post)) {
removeFinalizers(post.getMetadata(), Set.of(FINALIZER_NAME));
unPublishPost(post, events);
events.add(new PostDeletedEvent(this, post));
cleanUpResources(post);
// update post to be able to be collected by gc collector.
client.update(post);
Expand Down Expand Up @@ -126,7 +128,7 @@ public Result reconcile(Request request) {

// calculate the sha256sum
var configSha256sum = Hashing.sha256().hashString(post.getSpec().toString(), UTF_8)
.toString();
.toString();

var oldConfigChecksum = annotations.get(Constant.CHECKSUM_CONFIG_ANNO);
if (!Objects.equals(oldConfigChecksum, configSha256sum)) {
Expand Down

0 comments on commit 77c5bcf

Please sign in to comment.