summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2021-03-21 17:38:09 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2021-03-21 17:38:09 +0900
commit667d58bad4544d6e9dc75cfc4e6216179e2bc1aa (patch)
treed6ac2da158cece66b1815c9bab754e333628c7c8 /src
parentRevert "perf: Reduce database query" (diff)
downloadsharkey-667d58bad4544d6e9dc75cfc4e6216179e2bc1aa.tar.gz
sharkey-667d58bad4544d6e9dc75cfc4e6216179e2bc1aa.tar.bz2
sharkey-667d58bad4544d6e9dc75cfc4e6216179e2bc1aa.zip
better note read handling
Diffstat (limited to 'src')
-rw-r--r--src/client/components/note-detailed.vue3
-rw-r--r--src/client/components/note.vue3
-rw-r--r--src/client/ui/chat/note.vue3
-rw-r--r--src/daemons/janitor.ts2
-rw-r--r--src/server/api/endpoints/notes/mentions.ts4
-rw-r--r--src/server/api/stream/channels/antenna.ts2
-rw-r--r--src/server/api/stream/channels/channel.ts2
-rw-r--r--src/server/api/stream/channels/global-timeline.ts2
-rw-r--r--src/server/api/stream/channels/hashtag.ts2
-rw-r--r--src/server/api/stream/channels/home-timeline.ts2
-rw-r--r--src/server/api/stream/channels/hybrid-timeline.ts2
-rw-r--r--src/server/api/stream/channels/local-timeline.ts2
-rw-r--r--src/server/api/stream/channels/main.ts8
-rw-r--r--src/server/api/stream/index.ts58
-rw-r--r--src/services/note/read.ts80
15 files changed, 109 insertions, 66 deletions
diff --git a/src/client/components/note-detailed.vue b/src/client/components/note-detailed.vue
index 1ef3f43389..ea26d31100 100644
--- a/src/client/components/note-detailed.vue
+++ b/src/client/components/note-detailed.vue
@@ -350,7 +350,8 @@ export default defineComponent({
capture(withHandler = false) {
if (this.$i) {
- this.connection.send(document.body.contains(this.$el) ? 'sn' : 's', { id: this.appearNote.id });
+ // TODO: このノートがストリーミング経由で流れてきた場合のみ sr する
+ this.connection.send(document.body.contains(this.$el) ? 'sr' : 's', { id: this.appearNote.id });
if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
}
},
diff --git a/src/client/components/note.vue b/src/client/components/note.vue
index 65e09b7802..70f49fef7e 100644
--- a/src/client/components/note.vue
+++ b/src/client/components/note.vue
@@ -325,7 +325,8 @@ export default defineComponent({
capture(withHandler = false) {
if (this.$i) {
- this.connection.send(document.body.contains(this.$el) ? 'sn' : 's', { id: this.appearNote.id });
+ // TODO: このノートがストリーミング経由で流れてきた場合のみ sr する
+ this.connection.send(document.body.contains(this.$el) ? 'sr' : 's', { id: this.appearNote.id });
if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
}
},
diff --git a/src/client/ui/chat/note.vue b/src/client/ui/chat/note.vue
index 5a4a13d889..97275875ca 100644
--- a/src/client/ui/chat/note.vue
+++ b/src/client/ui/chat/note.vue
@@ -325,7 +325,8 @@ export default defineComponent({
capture(withHandler = false) {
if (this.$i) {
- this.connection.send(document.body.contains(this.$el) ? 'sn' : 's', { id: this.appearNote.id });
+ // TODO: このノートがストリーミング経由で流れてきた場合のみ sr する
+ this.connection.send(document.body.contains(this.$el) ? 'sr' : 's', { id: this.appearNote.id });
if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
}
},
diff --git a/src/daemons/janitor.ts b/src/daemons/janitor.ts
index 462ebf915c..c079086427 100644
--- a/src/daemons/janitor.ts
+++ b/src/daemons/janitor.ts
@@ -1,3 +1,5 @@
+// TODO: 消したい
+
const interval = 30 * 60 * 1000;
import { AttestationChallenges } from '../models';
import { LessThan } from 'typeorm';
diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts
index 30844774e0..30368ea578 100644
--- a/src/server/api/endpoints/notes/mentions.ts
+++ b/src/server/api/endpoints/notes/mentions.ts
@@ -83,9 +83,7 @@ export default define(meta, async (ps, user) => {
const mentions = await query.take(ps.limit!).getMany();
- for (const note of mentions) {
- read(user.id, note.id);
- }
+ read(user.id, mentions.map(note => note.id));
return await Notes.packMany(mentions, user);
});
diff --git a/src/server/api/stream/channels/antenna.ts b/src/server/api/stream/channels/antenna.ts
index b5a792f814..36a474f2ac 100644
--- a/src/server/api/stream/channels/antenna.ts
+++ b/src/server/api/stream/channels/antenna.ts
@@ -27,6 +27,8 @@ export default class extends Channel {
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (isMutedUserRelated(note, this.muting)) return;
+ this.connection.cacheNote(note);
+
this.send('note', note);
} else {
this.send(type, body);
diff --git a/src/server/api/stream/channels/channel.ts b/src/server/api/stream/channels/channel.ts
index aa570d1ef4..47a52465b2 100644
--- a/src/server/api/stream/channels/channel.ts
+++ b/src/server/api/stream/channels/channel.ts
@@ -43,6 +43,8 @@ export default class extends Channel {
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (isMutedUserRelated(note, this.muting)) return;
+ this.connection.cacheNote(note);
+
this.send('note', note);
}
diff --git a/src/server/api/stream/channels/global-timeline.ts b/src/server/api/stream/channels/global-timeline.ts
index 8c97e67226..8353f45323 100644
--- a/src/server/api/stream/channels/global-timeline.ts
+++ b/src/server/api/stream/channels/global-timeline.ts
@@ -56,6 +56,8 @@ export default class extends Channel {
// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
+ this.connection.cacheNote(note);
+
this.send('note', note);
}
diff --git a/src/server/api/stream/channels/hashtag.ts b/src/server/api/stream/channels/hashtag.ts
index 41447039d5..1b7f8efcc1 100644
--- a/src/server/api/stream/channels/hashtag.ts
+++ b/src/server/api/stream/channels/hashtag.ts
@@ -37,6 +37,8 @@ export default class extends Channel {
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (isMutedUserRelated(note, this.muting)) return;
+ this.connection.cacheNote(note);
+
this.send('note', note);
}
diff --git a/src/server/api/stream/channels/home-timeline.ts b/src/server/api/stream/channels/home-timeline.ts
index 6cfa6eae7b..59ba31c316 100644
--- a/src/server/api/stream/channels/home-timeline.ts
+++ b/src/server/api/stream/channels/home-timeline.ts
@@ -64,6 +64,8 @@ export default class extends Channel {
// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
+ this.connection.cacheNote(note);
+
this.send('note', note);
}
diff --git a/src/server/api/stream/channels/hybrid-timeline.ts b/src/server/api/stream/channels/hybrid-timeline.ts
index a9e577cacb..9715e9973f 100644
--- a/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/src/server/api/stream/channels/hybrid-timeline.ts
@@ -73,6 +73,8 @@ export default class extends Channel {
// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
+ this.connection.cacheNote(note);
+
this.send('note', note);
}
diff --git a/src/server/api/stream/channels/local-timeline.ts b/src/server/api/stream/channels/local-timeline.ts
index a3a5e491fc..e159c72d60 100644
--- a/src/server/api/stream/channels/local-timeline.ts
+++ b/src/server/api/stream/channels/local-timeline.ts
@@ -58,6 +58,8 @@ export default class extends Channel {
// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
+ this.connection.cacheNote(note);
+
this.send('note', note);
}
diff --git a/src/server/api/stream/channels/main.ts b/src/server/api/stream/channels/main.ts
index b69c2ec355..780bc0b89f 100644
--- a/src/server/api/stream/channels/main.ts
+++ b/src/server/api/stream/channels/main.ts
@@ -18,18 +18,22 @@ export default class extends Channel {
case 'notification': {
if (this.muting.has(body.userId)) return;
if (body.note && body.note.isHidden) {
- body.note = await Notes.pack(body.note.id, this.user, {
+ const note = await Notes.pack(body.note.id, this.user, {
detail: true
});
+ this.connection.cacheNote(note);
+ body.note = note;
}
break;
}
case 'mention': {
if (this.muting.has(body.userId)) return;
if (body.isHidden) {
- body = await Notes.pack(body.id, this.user, {
+ const note = await Notes.pack(body.id, this.user, {
detail: true
});
+ this.connection.cacheNote(note);
+ body = note;
}
break;
}
diff --git a/src/server/api/stream/index.ts b/src/server/api/stream/index.ts
index f67faee1ce..99ae558696 100644
--- a/src/server/api/stream/index.ts
+++ b/src/server/api/stream/index.ts
@@ -14,6 +14,7 @@ import { AccessToken } from '../../../models/entities/access-token';
import { UserProfile } from '../../../models/entities/user-profile';
import { publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from '../../../services/stream';
import { UserGroup } from '../../../models/entities/user-group';
+import { PackedNote } from '../../../models/repositories/note';
/**
* Main stream connection
@@ -29,6 +30,7 @@ export default class Connection {
public subscriber: EventEmitter;
private channels: Channel[] = [];
private subscribingNotes: any = {};
+ private cachedNotes: PackedNote[] = [];
constructor(
wsConnection: websocket.connection,
@@ -115,9 +117,9 @@ export default class Connection {
switch (type) {
case 'api': this.onApiRequest(body); break;
case 'readNotification': this.onReadNotification(body); break;
- case 'subNote': this.onSubscribeNote(body, true); break;
- case 'sn': this.onSubscribeNote(body, true); break; // alias
- case 's': this.onSubscribeNote(body, false); break;
+ case 'subNote': this.onSubscribeNote(body); break;
+ case 's': this.onSubscribeNote(body); break; // alias
+ case 'sr': this.onSubscribeNote(body); this.readNote(body); break;
case 'unsubNote': this.onUnsubscribeNote(body); break;
case 'un': this.onUnsubscribeNote(body); break; // alias
case 'connect': this.onChannelConnectRequested(body); break;
@@ -138,6 +140,48 @@ export default class Connection {
this.sendMessageToWs(type, body);
}
+ @autobind
+ public cacheNote(note: PackedNote) {
+ const add = (note: PackedNote) => {
+ const existIndex = this.cachedNotes.findIndex(n => n.id === note.id);
+ if (existIndex > -1) {
+ this.cachedNotes[existIndex] = note;
+ return;
+ }
+
+ this.cachedNotes.unshift(note);
+ if (this.cachedNotes.length > 32) {
+ this.cachedNotes.splice(32);
+ }
+ };
+
+ add(note);
+ if (note.reply) add(note.reply);
+ if (note.renote) add(note.renote);
+ }
+
+ @autobind
+ private readNote(body: any) {
+ const id = body.id;
+
+ const note = this.cachedNotes.find(n => n.id === id);
+ if (note == null) return;
+
+ if (this.user && (note.userId !== this.user.id)) {
+ if (note.mentions && note.mentions.includes(this.user.id)) {
+ readNote(this.user.id, [note]);
+ } else if (note.visibleUserIds && note.visibleUserIds.includes(this.user.id)) {
+ readNote(this.user.id, [note]);
+ }
+
+ if (this.followingChannels.has(note.channelId)) {
+ // TODO
+ }
+
+ // TODO: アンテナの既読処理
+ }
+ }
+
/**
* APIリクエスト要求時
*/
@@ -174,7 +218,7 @@ export default class Connection {
* 投稿購読要求時
*/
@autobind
- private onSubscribeNote(payload: any, read: boolean) {
+ private onSubscribeNote(payload: any) {
if (!payload.id) return;
if (this.subscribingNotes[payload.id] == null) {
@@ -186,12 +230,6 @@ export default class Connection {
if (this.subscribingNotes[payload.id] === 1) {
this.subscriber.on(`noteStream:${payload.id}`, this.onNoteStreamMessage);
}
-
- if (this.user && read) {
- // TODO: クライアントでタイムライン読み込みなどすると、一度に大量のreadNoteが発生しクエリ数がすごいことになるので、ある程度まとめてreadNoteするようにする
- // 具体的には、この箇所ではキュー的な配列にread予定ノートを溜めておくに留めて、別の箇所で定期的にキューにあるノートを配列でreadNoteに渡すような実装にする
- readNote(this.user.id, payload.id);
- }
}
/**
diff --git a/src/services/note/read.ts b/src/services/note/read.ts
index 5a39ab30b7..35279db411 100644
--- a/src/services/note/read.ts
+++ b/src/services/note/read.ts
@@ -2,70 +2,54 @@ import { publishMainStream } from '../stream';
import { Note } from '../../models/entities/note';
import { User } from '../../models/entities/user';
import { NoteUnreads, Antennas, AntennaNotes, Users } from '../../models';
-import { Not, IsNull } from 'typeorm';
+import { Not, IsNull, In } from 'typeorm';
/**
- * Mark a note as read
+ * Mark notes as read
*/
export default async function(
userId: User['id'],
- noteId: Note['id']
+ noteIds: Note['id'][]
) {
async function careNoteUnreads() {
- const exist = await NoteUnreads.findOne({
- userId: userId,
- noteId: noteId,
- });
-
- if (!exist) return;
-
// Remove the record
await NoteUnreads.delete({
userId: userId,
- noteId: noteId,
+ noteId: In(noteIds),
});
- if (exist.isMentioned) {
- NoteUnreads.count({
- userId: userId,
- isMentioned: true
- }).then(mentionsCount => {
- if (mentionsCount === 0) {
- // 全て既読になったイベントを発行
- publishMainStream(userId, 'readAllUnreadMentions');
- }
- });
- }
+ NoteUnreads.count({
+ userId: userId,
+ isMentioned: true
+ }).then(mentionsCount => {
+ if (mentionsCount === 0) {
+ // 全て既読になったイベントを発行
+ publishMainStream(userId, 'readAllUnreadMentions');
+ }
+ });
- if (exist.isSpecified) {
- NoteUnreads.count({
- userId: userId,
- isSpecified: true
- }).then(specifiedCount => {
- if (specifiedCount === 0) {
- // 全て既読になったイベントを発行
- publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
- }
- });
- }
+ NoteUnreads.count({
+ userId: userId,
+ isSpecified: true
+ }).then(specifiedCount => {
+ if (specifiedCount === 0) {
+ // 全て既読になったイベントを発行
+ publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
+ }
+ });
- if (exist.noteChannelId) {
- NoteUnreads.count({
- userId: userId,
- noteChannelId: Not(IsNull())
- }).then(channelNoteCount => {
- if (channelNoteCount === 0) {
- // 全て既読になったイベントを発行
- publishMainStream(userId, 'readAllChannels');
- }
- });
- }
+ NoteUnreads.count({
+ userId: userId,
+ noteChannelId: Not(IsNull())
+ }).then(channelNoteCount => {
+ if (channelNoteCount === 0) {
+ // 全て既読になったイベントを発行
+ publishMainStream(userId, 'readAllChannels');
+ }
+ });
}
async function careAntenna() {
- const beforeUnread = await Users.getHasUnreadAntenna(userId);
- if (!beforeUnread) return;
-
const antennas = await Antennas.find({ userId });
await Promise.all(antennas.map(async antenna => {
@@ -78,7 +62,7 @@ export default async function(
await AntennaNotes.update({
antennaId: antenna.id,
- noteId: noteId
+ noteId: In(noteIds)
}, {
read: true
});