Skip to content

Commit 17e1826

Browse files
committed
feat: improve text chat record loading diagnostics
1 parent 36edaa7 commit 17e1826

7 files changed

Lines changed: 564 additions & 36 deletions

File tree

frontend/composables/chat/useChatSearch.js

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,33 @@ if (scope === 'global') {
457457
}
458458
return (text.charAt(0) || '?').toString()
459459
}
460+
461+
const buildTransientSearchTargetContact = ({ username, displayName = '', avatar = '', isGroup = null } = {}) => {
462+
const u = String(username || '').trim()
463+
if (!u) return null
464+
const name = String(displayName || u).trim() || u
465+
return {
466+
id: u,
467+
username: u,
468+
name,
469+
avatar: String(avatar || '').trim() || null,
470+
avatarColor: '#4B5563',
471+
lastMessage: '',
472+
lastMessageTime: '',
473+
unreadCount: 0,
474+
isGroup: typeof isGroup === 'boolean' ? isGroup : u.endsWith('@chatroom'),
475+
isTop: false
476+
}
477+
}
478+
479+
const resolveSearchTargetContact = ({ username, displayName = '', avatar = '', isGroup = null } = {}) => {
480+
const u = String(username || '').trim()
481+
if (!u) return null
482+
const existing = contacts.value.find((c) => String(c?.username || '').trim() === u)
483+
if (existing) return existing
484+
if (String(selectedContact.value?.username || '').trim() === u) return selectedContact.value
485+
return buildTransientSearchTargetContact({ username: u, displayName, avatar, isGroup })
486+
}
460487
const searchContextBannerText = computed(() => {
461488
if (!searchContext.value?.active) return ''
462489
const kind = String(searchContext.value.kind || 'search')
@@ -986,7 +1013,12 @@ if (!hit?.id) return
9861013
const targetUsername = String(hit?.username || selectedContact.value?.username || '').trim()
9871014
if (!targetUsername) return
9881015

989-
const targetContact = contacts.value.find((c) => c?.username === targetUsername)
1016+
const targetContact = resolveSearchTargetContact({
1017+
username: targetUsername,
1018+
displayName: String(hit?.conversationName || hit?.username || targetUsername).trim(),
1019+
avatar: String(hit?.conversationAvatar || hit?.senderAvatar || '').trim(),
1020+
isGroup: targetUsername.endsWith('@chatroom')
1021+
})
9901022
if (targetContact && selectedContact.value?.username !== targetUsername) {
9911023
await selectContact(targetContact, { skipLoadMessages: true })
9921024
}
@@ -1051,7 +1083,12 @@ const u = String(targetUsername || selectedContact.value?.username || '').trim()
10511083
const anchor = String(anchorId || '').trim()
10521084
if (!u || !anchor) return
10531085

1054-
const targetContact = contacts.value.find((c) => c?.username === u)
1086+
const targetContact = resolveSearchTargetContact({
1087+
username: u,
1088+
displayName: String(selectedContact.value?.name || u).trim(),
1089+
avatar: String(selectedContact.value?.avatar || '').trim(),
1090+
isGroup: u.endsWith('@chatroom')
1091+
})
10551092
if (targetContact && selectedContact.value?.username !== u) {
10561093
await selectContact(targetContact, { skipLoadMessages: true })
10571094
}

frontend/pages/chat/[[username]].vue

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,23 @@ const nextMessageLoadToken = () => {
119119
return messageLoadSequence
120120
}
121121
122+
const buildTransientContact = ({ username, name = '', avatar = '', isGroup = null } = {}) => {
123+
const u = String(username || '').trim()
124+
const displayName = String(name || u).trim() || u
125+
return {
126+
id: u,
127+
username: u,
128+
name: displayName,
129+
avatar: String(avatar || '').trim() || null,
130+
avatarColor: '#4B5563',
131+
lastMessage: '',
132+
lastMessageTime: '',
133+
unreadCount: 0,
134+
isGroup: typeof isGroup === 'boolean' ? isGroup : u.endsWith('@chatroom'),
135+
isTop: false
136+
}
137+
}
138+
122139
const buildChatPath = (username) => {
123140
return username ? `/chat/${encodeURIComponent(username)}` : '/chat'
124141
}
@@ -334,14 +351,28 @@ const selectContact = async (contact, options = {}) => {
334351
}
335352
336353
const applyRouteSelection = async (options = {}) => {
354+
const selectionReason = String(options.reason || 'route-selection').trim() || 'route-selection'
355+
const requested = routeUsername.value || ''
356+
if ((!contacts.value || contacts.value.length === 0) && requested) {
357+
if (selectedContact.value?.username === requested) {
358+
return
359+
}
360+
await selectContact(buildTransientContact({ username: requested }), {
361+
syncRoute: false,
362+
deferLoadMessages: !!options.deferLoadMessages,
363+
reason: `${selectionReason}:transient-route-empty-list`
364+
})
365+
return
366+
}
337367
if (!contacts.value || contacts.value.length === 0) {
338368
selectedContact.value = null
339369
return
340370
}
341371
342-
const selectionReason = String(options.reason || 'route-selection').trim() || 'route-selection'
343-
const requested = routeUsername.value || ''
344372
if (requested) {
373+
if (selectedContact.value?.username === requested) {
374+
return
375+
}
345376
const matched = contacts.value.find((contact) => contact.username === requested)
346377
if (matched) {
347378
if (selectedContact.value?.username !== matched.username) {
@@ -353,6 +384,12 @@ const applyRouteSelection = async (options = {}) => {
353384
}
354385
return
355386
}
387+
await selectContact(buildTransientContact({ username: requested }), {
388+
syncRoute: false,
389+
deferLoadMessages: !!options.deferLoadMessages,
390+
reason: `${selectionReason}:transient-route`
391+
})
392+
return
356393
}
357394
358395
await selectContact(contacts.value[0], {

src/wechat_decrypt_tool/chat_helpers.py

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from .app_paths import get_output_databases_dir
1616
from .logging_config import get_logger
17+
from .sqlite_diagnostics import collect_sqlite_diagnostics, format_sqlite_diagnostics
1718

1819
try:
1920
import zstandard as zstd # type: ignore
@@ -1755,9 +1756,10 @@ def _load_latest_message_previews(account_dir: Path, usernames: list[str]) -> di
17551756

17561757
session_db_path = Path(account_dir) / "session.db"
17571758
if session_db_path.exists() and remaining:
1758-
sconn = sqlite3.connect(str(session_db_path))
1759-
sconn.row_factory = sqlite3.Row
1759+
sconn: Optional[sqlite3.Connection] = None
17601760
try:
1761+
sconn = sqlite3.connect(str(session_db_path))
1762+
sconn.row_factory = sqlite3.Row
17611763
uniq = list(dict.fromkeys([u for u in remaining if u]))
17621764
chunk_size = 900
17631765
for i in range(0, len(uniq), chunk_size):
@@ -1786,10 +1788,24 @@ def _load_latest_message_previews(account_dir: Path, usernames: list[str]) -> di
17861788
if not u:
17871789
continue
17881790
expected_ts_by_user[u] = int(r["last_timestamp"] or 0)
1791+
except sqlite3.DatabaseError as e:
1792+
expected_ts_by_user = {}
1793+
logger.warning(
1794+
"[sessions.preview] session timestamp lookup failed account=%s db=%s usernames=%s sample_usernames=%s error=%s diag=%s",
1795+
account_dir.name,
1796+
str(session_db_path),
1797+
len(remaining),
1798+
sorted([u for u in remaining if u])[:5],
1799+
str(e),
1800+
format_sqlite_diagnostics(
1801+
collect_sqlite_diagnostics(session_db_path, quick_check=True, table_name="SessionTable")
1802+
),
1803+
)
17891804
except Exception:
17901805
expected_ts_by_user = {}
17911806
finally:
1792-
sconn.close()
1807+
if sconn is not None:
1808+
sconn.close()
17931809

17941810
if _DEBUG_SESSIONS:
17951811
logger.info(
@@ -1800,9 +1816,16 @@ def _load_latest_message_previews(account_dir: Path, usernames: list[str]) -> di
18001816
)
18011817

18021818
for db_path in db_paths:
1803-
conn = sqlite3.connect(str(db_path))
1804-
conn.row_factory = sqlite3.Row
1819+
conn: Optional[sqlite3.Connection] = None
1820+
stage = "connect"
1821+
stage_username = ""
1822+
stage_table = ""
18051823
try:
1824+
stage = "connect"
1825+
conn = sqlite3.connect(str(db_path))
1826+
conn.row_factory = sqlite3.Row
1827+
1828+
stage = "sqlite_master"
18061829
rows = conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
18071830
names = [str(r[0]) for r in rows if r and r[0]]
18081831
lower_to_actual = {n.lower(): n for n in names}
@@ -1818,9 +1841,12 @@ def _load_latest_message_previews(account_dir: Path, usernames: list[str]) -> di
18181841

18191842
conn.text_factory = bytes
18201843
for u, tn in found.items():
1844+
stage_username = str(u)
1845+
stage_table = str(tn)
18211846
quoted = _quote_ident(tn)
18221847
try:
18231848
try:
1849+
stage = "latest_row_with_name2id"
18241850
r = conn.execute(
18251851
"SELECT "
18261852
"m.local_type, m.message_content, m.compress_content, m.create_time, m.sort_seq, m.local_id, "
@@ -1831,13 +1857,28 @@ def _load_latest_message_previews(account_dir: Path, usernames: list[str]) -> di
18311857
"LIMIT 1"
18321858
).fetchone()
18331859
except Exception:
1860+
stage = "latest_row_without_name2id"
18341861
r = conn.execute(
18351862
"SELECT "
18361863
"local_type, message_content, compress_content, create_time, sort_seq, local_id, '' AS sender_username "
18371864
f"FROM {quoted} "
18381865
"ORDER BY sort_seq DESC, local_id DESC "
18391866
"LIMIT 1"
18401867
).fetchone()
1868+
except sqlite3.DatabaseError as e:
1869+
logger.warning(
1870+
"[sessions.preview] latest row query failed account=%s db=%s username=%s table=%s stage=%s error=%s diag=%s",
1871+
account_dir.name,
1872+
str(db_path),
1873+
str(u),
1874+
str(tn),
1875+
stage,
1876+
str(e),
1877+
format_sqlite_diagnostics(
1878+
collect_sqlite_diagnostics(db_path, quick_check=True, table_name=tn)
1879+
),
1880+
)
1881+
continue
18411882
except Exception as e:
18421883
if _DEBUG_SESSIONS:
18431884
logger.info(
@@ -1855,6 +1896,7 @@ def _load_latest_message_previews(account_dir: Path, usernames: list[str]) -> di
18551896
expected_ts = int(expected_ts_by_user.get(u) or 0)
18561897
if expected_ts > 0 and create_time > 0 and create_time < expected_ts:
18571898
try:
1899+
stage = "latest_row_by_create_time_with_name2id"
18581900
r2 = conn.execute(
18591901
"SELECT "
18601902
"m.local_type, m.message_content, m.compress_content, m.create_time, m.sort_seq, m.local_id, "
@@ -1866,13 +1908,28 @@ def _load_latest_message_previews(account_dir: Path, usernames: list[str]) -> di
18661908
).fetchone()
18671909
except Exception:
18681910
try:
1911+
stage = "latest_row_by_create_time_without_name2id"
18691912
r2 = conn.execute(
18701913
"SELECT "
18711914
"local_type, message_content, compress_content, create_time, sort_seq, local_id, '' AS sender_username "
18721915
f"FROM {quoted} "
18731916
"ORDER BY COALESCE(create_time, 0) DESC, COALESCE(sort_seq, 0) DESC, local_id DESC "
18741917
"LIMIT 1"
18751918
).fetchone()
1919+
except sqlite3.DatabaseError as e:
1920+
logger.warning(
1921+
"[sessions.preview] latest row requery failed account=%s db=%s username=%s table=%s stage=%s error=%s diag=%s",
1922+
account_dir.name,
1923+
str(db_path),
1924+
str(u),
1925+
str(tn),
1926+
stage,
1927+
str(e),
1928+
format_sqlite_diagnostics(
1929+
collect_sqlite_diagnostics(db_path, quick_check=True, table_name=tn)
1930+
),
1931+
)
1932+
r2 = None
18761933
except Exception:
18771934
r2 = None
18781935

@@ -1900,8 +1957,28 @@ def _load_latest_message_previews(account_dir: Path, usernames: list[str]) -> di
19001957
prev = best.get(u)
19011958
if prev is None or sort_key > prev[0]:
19021959
best[u] = (sort_key, preview)
1960+
except sqlite3.DatabaseError as e:
1961+
logger.warning(
1962+
"[sessions.preview] malformed message db account=%s db=%s stage=%s username=%s table=%s remaining=%s sample_usernames=%s error=%s diag=%s",
1963+
account_dir.name,
1964+
str(db_path),
1965+
stage,
1966+
stage_username,
1967+
stage_table,
1968+
len(remaining),
1969+
sorted([u for u in remaining if u])[:5],
1970+
str(e),
1971+
format_sqlite_diagnostics(
1972+
collect_sqlite_diagnostics(db_path, quick_check=True, table_name=(stage_table or None))
1973+
),
1974+
)
1975+
continue
19031976
finally:
1904-
conn.close()
1977+
if conn is not None:
1978+
try:
1979+
conn.close()
1980+
except Exception:
1981+
pass
19051982

19061983
previews = {u: v[1] for u, v in best.items() if v and v[1]}
19071984
if _DEBUG_SESSIONS:

0 commit comments

Comments
 (0)