Skip to content

Commit a8ac906

Browse files
2977094657codex
andcommitted
Add logging and sync guards around chat search decrypt flow
Co-authored-by: Codex <noreply@openai.com>
1 parent 17e1826 commit a8ac906

4 files changed

Lines changed: 507 additions & 194 deletions

File tree

frontend/composables/chat/useChatSearch.js

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,28 @@ export const useChatSearch = ({
4343
selectContact,
4444
loadMoreMessages
4545
}) => {
46+
const isDesktopRenderer = () => {
47+
if (!process.client || typeof window === 'undefined') return false
48+
return !!window.wechatDesktop?.__brand
49+
}
50+
51+
const logSearchPhase = (phase, details = {}) => {
52+
const payload = {
53+
account: String(selectedAccount.value || '').trim(),
54+
selectedUsername: String(selectedContact.value?.username || '').trim(),
55+
contextUsername: String(searchContext.value?.username || '').trim(),
56+
...details
57+
}
58+
59+
if (isDesktopRenderer()) {
60+
try {
61+
window.wechatDesktop?.logDebug?.('chat-search', phase, payload)
62+
} catch {}
63+
}
64+
65+
console.info(`[chat-search] ${phase}`, payload)
66+
}
67+
4668
const messageSearchOpen = ref(false)
4769
const messageSearchQuery = ref('')
4870
const messageSearchScope = ref('global') // conversation | global
@@ -1007,24 +1029,65 @@ updateJumpToBottomState()
10071029

10081030
const locateSearchHit = async (hit) => {
10091031
if (!process.client) return
1010-
if (!selectedAccount.value) return
1011-
if (!hit?.id) return
1032+
if (!selectedAccount.value) {
1033+
logSearchPhase('locateSearchHit:skip:no-account', {
1034+
hitId: String(hit?.id || '').trim(),
1035+
hitUsername: String(hit?.username || '').trim()
1036+
})
1037+
return
1038+
}
1039+
if (!hit?.id) {
1040+
logSearchPhase('locateSearchHit:skip:missing-hit-id', {
1041+
hitKeys: Object.keys(hit || {})
1042+
})
1043+
return
1044+
}
10121045

10131046
const targetUsername = String(hit?.username || selectedContact.value?.username || '').trim()
1014-
if (!targetUsername) return
1047+
if (!targetUsername) {
1048+
logSearchPhase('locateSearchHit:skip:missing-target-username', {
1049+
hitId: String(hit?.id || '').trim(),
1050+
hitUsername: String(hit?.username || '').trim(),
1051+
selectedUsernameFallback: String(selectedContact.value?.username || '').trim()
1052+
})
1053+
return
1054+
}
1055+
1056+
logSearchPhase('locateSearchHit:start', {
1057+
hitId: String(hit?.id || '').trim(),
1058+
hitUsername: String(hit?.username || '').trim(),
1059+
targetUsername,
1060+
conversationName: String(hit?.conversationName || '').trim()
1061+
})
10151062

10161063
const targetContact = resolveSearchTargetContact({
10171064
username: targetUsername,
10181065
displayName: String(hit?.conversationName || hit?.username || targetUsername).trim(),
10191066
avatar: String(hit?.conversationAvatar || hit?.senderAvatar || '').trim(),
10201067
isGroup: targetUsername.endsWith('@chatroom')
10211068
})
1069+
logSearchPhase('locateSearchHit:target-resolved', {
1070+
hitId: String(hit?.id || '').trim(),
1071+
targetUsername,
1072+
contactResolved: !!targetContact,
1073+
contactSource: targetContact
1074+
? (contacts.value.find((c) => String(c?.username || '').trim() === targetUsername) ? 'contacts' : 'transient')
1075+
: 'none'
1076+
})
10221077
if (targetContact && selectedContact.value?.username !== targetUsername) {
10231078
await selectContact(targetContact, { skipLoadMessages: true })
1079+
logSearchPhase('locateSearchHit:selectContact:done', {
1080+
hitId: String(hit?.id || '').trim(),
1081+
targetUsername
1082+
})
10241083
}
10251084

10261085
if (searchContext.value?.active && searchContext.value.username !== targetUsername) {
10271086
await exitSearchContext()
1087+
logSearchPhase('locateSearchHit:exitSearchContext:done', {
1088+
hitId: String(hit?.id || '').trim(),
1089+
targetUsername
1090+
})
10281091
}
10291092

10301093
if (!searchContext.value?.active) {
@@ -1053,6 +1116,12 @@ if (!searchContext.value?.active) {
10531116
}
10541117

10551118
try {
1119+
logSearchPhase('locateSearchHit:messagesAround:start', {
1120+
hitId: String(hit?.id || '').trim(),
1121+
targetUsername,
1122+
before: 35,
1123+
after: 35
1124+
})
10561125
const resp = await api.getChatMessagesAround({
10571126
account: selectedAccount.value,
10581127
username: targetUsername,
@@ -1065,13 +1134,31 @@ try {
10651134
const mapped = raw.map(normalizeMessage)
10661135
allMessages.value = { ...allMessages.value, [targetUsername]: mapped }
10671136
messagesMeta.value = { ...messagesMeta.value, [targetUsername]: { total: mapped.length, hasMore: false } }
1137+
logSearchPhase('locateSearchHit:messagesAround:end', {
1138+
hitId: String(hit?.id || '').trim(),
1139+
targetUsername,
1140+
messageCount: mapped.length,
1141+
anchorId: String(resp?.anchorId || hit?.id || '').trim(),
1142+
anchorIndex: Number(resp?.anchorIndex ?? -1)
1143+
})
10681144

10691145
searchContext.value.anchorId = String(resp?.anchorId || hit.id)
10701146
searchContext.value.anchorIndex = Number(resp?.anchorIndex ?? -1)
10711147

10721148
const ok = await scrollToMessageId(searchContext.value.anchorId)
1149+
logSearchPhase('locateSearchHit:scroll:end', {
1150+
hitId: String(hit?.id || '').trim(),
1151+
targetUsername,
1152+
anchorId: String(searchContext.value.anchorId || '').trim(),
1153+
scrollFound: !!ok
1154+
})
10731155
if (ok) flashMessage(searchContext.value.anchorId)
10741156
} catch (e) {
1157+
logSearchPhase('locateSearchHit:error', {
1158+
hitId: String(hit?.id || '').trim(),
1159+
targetUsername,
1160+
error: String(e?.message || e || '')
1161+
})
10751162
window.alert(e?.message || '定位失败')
10761163
}
10771164
}
@@ -1356,6 +1443,11 @@ try {
13561443

13571444
const onSearchHitClick = async (hit, idx) => {
13581445
messageSearchSelectedIndex.value = Number(idx || 0)
1446+
logSearchPhase('onSearchHitClick', {
1447+
index: Number(idx || 0),
1448+
hitId: String(hit?.id || '').trim(),
1449+
hitUsername: String(hit?.username || '').trim()
1450+
})
13591451
await locateSearchHit(hit)
13601452
}
13611453

src/wechat_decrypt_tool/chat_realtime_autosync.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,62 @@ def __init__(self) -> None:
123123

124124
self._mu = threading.Lock()
125125
self._states: dict[str, _AccountState] = {}
126+
self._paused_accounts: dict[str, int] = {}
126127
self._stop = threading.Event()
127128
self._thread: Optional[threading.Thread] = None
128129

130+
def _is_account_paused_locked(self, account: str) -> bool:
131+
key = str(account or "").strip()
132+
if not key:
133+
return False
134+
return int(self._paused_accounts.get(key) or 0) > 0
135+
136+
def is_account_paused(self, account: str) -> bool:
137+
with self._mu:
138+
return self._is_account_paused_locked(account)
139+
140+
def pause_account(self, account: str, reason: str = "") -> int:
141+
key = str(account or "").strip()
142+
if not key:
143+
return 0
144+
145+
with self._mu:
146+
depth = int(self._paused_accounts.get(key) or 0) + 1
147+
self._paused_accounts[key] = depth
148+
st = self._states.get(key)
149+
if st is not None:
150+
st.due_at = 0.0
151+
152+
logger.info(
153+
"[realtime-autosync] pause account=%s reason=%s depth=%s",
154+
key,
155+
str(reason or "").strip() or "-",
156+
int(depth),
157+
)
158+
return depth
159+
160+
def resume_account(self, account: str, reason: str = "") -> int:
161+
key = str(account or "").strip()
162+
if not key:
163+
return 0
164+
165+
with self._mu:
166+
current = int(self._paused_accounts.get(key) or 0)
167+
if current <= 1:
168+
self._paused_accounts.pop(key, None)
169+
depth = 0
170+
else:
171+
depth = current - 1
172+
self._paused_accounts[key] = depth
173+
174+
logger.info(
175+
"[realtime-autosync] resume account=%s reason=%s depth=%s",
176+
key,
177+
str(reason or "").strip() or "-",
178+
int(depth),
179+
)
180+
return depth
181+
129182
def start(self) -> None:
130183
if not self._enabled:
131184
logger.info("[realtime-autosync] disabled by env WECHAT_TOOL_REALTIME_AUTOSYNC=0")
@@ -188,6 +241,12 @@ def _tick(self) -> None:
188241
if self._stop.is_set():
189242
break
190243

244+
if self.is_account_paused(acc):
245+
with self._mu:
246+
st = self._states.setdefault(acc, _AccountState())
247+
st.due_at = 0.0
248+
continue
249+
191250
try:
192251
account_dir = _resolve_account_dir(acc)
193252
except HTTPException:
@@ -238,6 +297,9 @@ def _tick(self) -> None:
238297
for acc, st in self._states.items():
239298
if running >= int(self._workers):
240299
break
300+
if self._is_account_paused_locked(acc):
301+
st.due_at = 0.0
302+
continue
241303
if st.due_at <= 0 or st.due_at > now:
242304
continue
243305
if st.thread is not None and st.thread.is_alive():
@@ -278,6 +340,9 @@ def _sync_account_runner(self, account: str) -> None:
278340
try:
279341
if self._stop.is_set() or (not account):
280342
return
343+
if self.is_account_paused(account):
344+
logger.info("[realtime-autosync] sync skipped account=%s reason=paused", account)
345+
return
281346
res = self._sync_account(account)
282347
inserted = int((res or {}).get("inserted_total") or (res or {}).get("insertedTotal") or 0)
283348
synced = int((res or {}).get("synced") or (res or {}).get("sessionsSynced") or 0)
@@ -297,6 +362,8 @@ def _sync_account(self, account: str) -> dict[str, Any]:
297362
account = str(account or "").strip()
298363
if not account:
299364
return {"status": "skipped", "reason": "missing account"}
365+
if self.is_account_paused(account):
366+
return {"status": "skipped", "reason": "paused"}
300367

301368
try:
302369
account_dir = _resolve_account_dir(account)

src/wechat_decrypt_tool/routers/chat.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7071,13 +7071,26 @@ async def get_chat_messages_around(
70717071
if after > 200:
70727072
after = 200
70737073

7074+
trace_id = f"msg-around-{int(time.time() * 1000)}-{threading.get_ident()}"
7075+
logger.info(
7076+
"[%s] chat messages around start account=%s username=%s anchor_id=%s before=%s after=%s",
7077+
trace_id,
7078+
str(account or "").strip(),
7079+
str(username or "").strip(),
7080+
str(anchor_id or "").strip(),
7081+
int(before),
7082+
int(after),
7083+
)
7084+
70747085
parts = str(anchor_id).split(":", 2)
70757086
if len(parts) != 3:
7087+
logger.warning("[%s] chat messages around invalid anchor format anchor_id=%s", trace_id, str(anchor_id or "").strip())
70767088
raise HTTPException(status_code=400, detail="Invalid anchor_id.")
70777089
anchor_db_stem, anchor_table_name_in, anchor_local_id_str = parts
70787090
try:
70797091
anchor_local_id = int(anchor_local_id_str)
70807092
except Exception:
7093+
logger.warning("[%s] chat messages around invalid anchor local_id anchor_id=%s", trace_id, str(anchor_id or "").strip())
70817094
raise HTTPException(status_code=400, detail="Invalid anchor_id.")
70827095

70837096
account_dir = _resolve_account_dir(account)
@@ -7093,6 +7106,13 @@ async def get_chat_messages_around(
70937106
anchor_db_path = p
70947107
break
70957108
if anchor_db_path is None:
7109+
logger.warning(
7110+
"[%s] chat messages around anchor db missing account=%s username=%s anchor_db=%s",
7111+
trace_id,
7112+
account_dir.name,
7113+
username,
7114+
anchor_db_stem,
7115+
)
70967116
raise HTTPException(status_code=404, detail="Anchor database not found.")
70977117

70987118
# Open resource DB once (optional), and reuse for all message DBs.
@@ -7491,6 +7511,18 @@ def sort_key_global(m: dict[str, Any]) -> tuple[int, int, str, int]:
74917511
head_image_db_path=head_image_db_path,
74927512
)
74937513

7514+
logger.info(
7515+
"[%s] chat messages around done account=%s username=%s anchor_id=%s canonical_anchor=%s anchor_index=%s returned=%s merged_total=%s",
7516+
trace_id,
7517+
account_dir.name,
7518+
username,
7519+
str(anchor_id or "").strip(),
7520+
anchor_id_canon,
7521+
int(anchor_index),
7522+
len(return_messages),
7523+
len(merged),
7524+
)
7525+
74947526
return {
74957527
"status": "success",
74967528
"account": account_dir.name,

0 commit comments

Comments
 (0)