forked from Archives/Athou_commafeed
the "new-feed-entries" websocket event no longer needs to reload the entire tree
This commit is contained in:
@@ -26,6 +26,22 @@ export const treeSlice = createSlice({
|
|||||||
toggleSidebar: state => {
|
toggleSidebar: state => {
|
||||||
state.sidebarVisible = !state.sidebarVisible
|
state.sidebarVisible = !state.sidebarVisible
|
||||||
},
|
},
|
||||||
|
incrementUnreadCount: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{
|
||||||
|
feedId: number
|
||||||
|
amount: number
|
||||||
|
}>
|
||||||
|
) => {
|
||||||
|
if (!state.rootCategory) return
|
||||||
|
visitCategoryTree(state.rootCategory, c =>
|
||||||
|
c.feeds
|
||||||
|
.filter(f => f.id === action.payload.feedId)
|
||||||
|
.forEach(f => {
|
||||||
|
f.unread += action.payload.amount
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: builder => {
|
extraReducers: builder => {
|
||||||
builder.addCase(reloadTree.fulfilled, (state, action) => {
|
builder.addCase(reloadTree.fulfilled, (state, action) => {
|
||||||
@@ -53,4 +69,4 @@ export const treeSlice = createSlice({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const { setMobileMenuOpen, toggleSidebar } = treeSlice.actions
|
export const { setMobileMenuOpen, toggleSidebar, incrementUnreadCount } = treeSlice.actions
|
||||||
|
|||||||
@@ -1,9 +1,22 @@
|
|||||||
import { setWebSocketConnected } from "app/server/slice"
|
import { setWebSocketConnected } from "app/server/slice"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { type AppDispatch, useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { reloadTree } from "app/tree/thunks"
|
import { incrementUnreadCount } from "app/tree/slice"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import WebsocketHeartbeatJs from "websocket-heartbeat-js"
|
import WebsocketHeartbeatJs from "websocket-heartbeat-js"
|
||||||
|
|
||||||
|
const handleMessage = (dispatch: AppDispatch, message: string) => {
|
||||||
|
const parts = message.split(":")
|
||||||
|
const type = parts[0]
|
||||||
|
if (type === "new-feed-entries") {
|
||||||
|
dispatch(
|
||||||
|
incrementUnreadCount({
|
||||||
|
feedId: +parts[1],
|
||||||
|
amount: +parts[2],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const useWebSocket = () => {
|
export const useWebSocket = () => {
|
||||||
const websocketEnabled = useAppSelector(state => state.server.serverInfos?.websocketEnabled)
|
const websocketEnabled = useAppSelector(state => state.server.serverInfos?.websocketEnabled)
|
||||||
const websocketPingInterval = useAppSelector(state => state.server.serverInfos?.websocketPingInterval)
|
const websocketPingInterval = useAppSelector(state => state.server.serverInfos?.websocketPingInterval)
|
||||||
@@ -27,7 +40,7 @@ export const useWebSocket = () => {
|
|||||||
ws.onmessage = event => {
|
ws.onmessage = event => {
|
||||||
const { data } = event
|
const { data } = event
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
if (data.startsWith("new-feed-entries:")) dispatch(reloadTree())
|
handleMessage(dispatch, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ public class FeedRefreshUpdater {
|
|||||||
|
|
||||||
public boolean update(Feed feed, List<Entry> entries) {
|
public boolean update(Feed feed, List<Entry> entries) {
|
||||||
boolean processed = true;
|
boolean processed = true;
|
||||||
boolean insertedAtLeastOneEntry = false;
|
long inserted = 0;
|
||||||
|
|
||||||
if (!entries.isEmpty()) {
|
if (!entries.isEmpty()) {
|
||||||
Set<String> lastEntries = cache.getLastEntries(feed);
|
Set<String> lastEntries = cache.getLastEntries(feed);
|
||||||
@@ -134,7 +134,7 @@ public class FeedRefreshUpdater {
|
|||||||
}
|
}
|
||||||
AddEntryResult addEntryResult = addEntry(feed, entry, subscriptions);
|
AddEntryResult addEntryResult = addEntry(feed, entry, subscriptions);
|
||||||
processed &= addEntryResult.processed;
|
processed &= addEntryResult.processed;
|
||||||
insertedAtLeastOneEntry |= addEntryResult.inserted;
|
inserted += addEntryResult.inserted ? 1 : 0;
|
||||||
|
|
||||||
entryCacheMiss.mark();
|
entryCacheMiss.mark();
|
||||||
} else {
|
} else {
|
||||||
@@ -148,13 +148,12 @@ public class FeedRefreshUpdater {
|
|||||||
|
|
||||||
if (subscriptions == null) {
|
if (subscriptions == null) {
|
||||||
feed.setMessage("No new entries found");
|
feed.setMessage("No new entries found");
|
||||||
} else if (insertedAtLeastOneEntry) {
|
} else if (inserted > 0) {
|
||||||
List<User> users = subscriptions.stream().map(FeedSubscription::getUser).toList();
|
List<User> users = subscriptions.stream().map(FeedSubscription::getUser).toList();
|
||||||
cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
|
cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
|
||||||
cache.invalidateUserRootCategory(users.toArray(new User[0]));
|
cache.invalidateUserRootCategory(users.toArray(new User[0]));
|
||||||
|
|
||||||
// notify over websocket
|
notifyOverWebsocket(subscriptions, inserted);
|
||||||
subscriptions.forEach(sub -> webSocketSessions.sendMessage(sub.getUser(), WebSocketMessageBuilder.newFeedEntries(sub)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +162,7 @@ public class FeedRefreshUpdater {
|
|||||||
feed.setDisabledUntil(Instant.EPOCH);
|
feed.setDisabledUntil(Instant.EPOCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (insertedAtLeastOneEntry) {
|
if (inserted > 0) {
|
||||||
feedUpdated.mark();
|
feedUpdated.mark();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,6 +171,10 @@ public class FeedRefreshUpdater {
|
|||||||
return processed;
|
return processed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void notifyOverWebsocket(List<FeedSubscription> subscriptions, long inserted) {
|
||||||
|
subscriptions.forEach(sub -> webSocketSessions.sendMessage(sub.getUser(), WebSocketMessageBuilder.newFeedEntries(sub, inserted)));
|
||||||
|
}
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
private static class AddEntryResult {
|
private static class AddEntryResult {
|
||||||
private final boolean processed;
|
private final boolean processed;
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import lombok.experimental.UtilityClass;
|
|||||||
@UtilityClass
|
@UtilityClass
|
||||||
public class WebSocketMessageBuilder {
|
public class WebSocketMessageBuilder {
|
||||||
|
|
||||||
public static String newFeedEntries(FeedSubscription subscription) {
|
public static String newFeedEntries(FeedSubscription subscription, long count) {
|
||||||
return String.format("%s:%s", "new-feed-entries", subscription.getId());
|
return String.format("%s:%s:%s", "new-feed-entries", subscription.getId(), count);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class WebSocketIT extends BaseIT {
|
|||||||
Long subscriptionId = subscribe(getFeedUrl());
|
Long subscriptionId = subscribe(getFeedUrl());
|
||||||
|
|
||||||
Awaitility.await().atMost(15, TimeUnit.SECONDS).until(() -> messageRef.get() != null);
|
Awaitility.await().atMost(15, TimeUnit.SECONDS).until(() -> messageRef.get() != null);
|
||||||
Assertions.assertEquals("new-feed-entries:" + subscriptionId, messageRef.get());
|
Assertions.assertEquals("new-feed-entries:" + subscriptionId + ":2", messageRef.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user