Skip to content

Commit c3e20cc

Browse files
committed
Fix realtime sync writing Msg tables
1 parent 7934c44 commit c3e20cc

2 files changed

Lines changed: 133 additions & 12 deletions

File tree

src/wechat_decrypt_tool/routers/chat.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,12 +1626,13 @@ def normalize(item: dict[str, Any]) -> dict[str, Any]:
16261626

16271627
inserted = 0
16281628
backfilled = 0
1629-
if new_rows and (not name2id_synced):
1630-
_best_effort_upsert_output_name2id_rows(
1631-
msg_conn,
1632-
account_name=account_dir.name,
1633-
rows=new_rows,
1634-
)
1629+
if new_rows:
1630+
if not name2id_synced:
1631+
_best_effort_upsert_output_name2id_rows(
1632+
msg_conn,
1633+
account_name=account_dir.name,
1634+
rows=new_rows,
1635+
)
16351636

16361637
# Insert older -> newer to keep sqlite btree locality similar to existing data.
16371638
values = [tuple(r.get(c) for c in insert_cols) for r in reversed(new_rows)]
@@ -1991,12 +1992,13 @@ def normalize(item: dict[str, Any]) -> dict[str, Any]:
19911992

19921993
inserted = 0
19931994
backfilled = 0
1994-
if new_rows and (not name2id_synced):
1995-
_best_effort_upsert_output_name2id_rows(
1996-
msg_conn,
1997-
account_name=account_dir.name,
1998-
rows=new_rows,
1999-
)
1995+
if new_rows:
1996+
if not name2id_synced:
1997+
_best_effort_upsert_output_name2id_rows(
1998+
msg_conn,
1999+
account_name=account_dir.name,
2000+
rows=new_rows,
2001+
)
20002002

20012003
values = [tuple(r.get(c) for c in insert_cols) for r in reversed(new_rows)]
20022004
insert_t0 = time.perf_counter()

tests/test_chat_realtime_name2id_sync.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,125 @@ def _fake_exec_query(_handle, *, kind, path, sql):
112112
],
113113
)
114114

115+
def test_sync_still_inserts_new_messages_when_name2id_is_up_to_date(self):
116+
with TemporaryDirectory() as td:
117+
account_dir = Path(td) / "acc"
118+
account_dir.mkdir(parents=True, exist_ok=True)
119+
120+
username = "wxid_friend"
121+
table_name = f"Msg_{hashlib.md5(username.encode('utf-8')).hexdigest()}"
122+
msg_db_path = account_dir / "message_0.db"
123+
124+
conn = sqlite3.connect(str(msg_db_path))
125+
try:
126+
conn.execute("CREATE TABLE Name2Id (user_name TEXT, is_session INTEGER DEFAULT 1)")
127+
conn.execute(
128+
"""
129+
CREATE TABLE "{table_name}" (
130+
local_id INTEGER PRIMARY KEY,
131+
server_id INTEGER,
132+
local_type INTEGER,
133+
sort_seq INTEGER,
134+
real_sender_id INTEGER,
135+
create_time INTEGER,
136+
message_content TEXT,
137+
compress_content BLOB,
138+
packed_info_data BLOB
139+
)
140+
""".format(table_name=table_name)
141+
)
142+
conn.execute("INSERT INTO Name2Id(rowid, user_name, is_session) VALUES (1, ?, 1)", ("acc",))
143+
conn.execute("INSERT INTO Name2Id(rowid, user_name, is_session) VALUES (2, ?, 1)", (username,))
144+
conn.execute(
145+
f'INSERT INTO "{table_name}" '
146+
"(local_id, server_id, local_type, sort_seq, real_sender_id, create_time, message_content, compress_content, packed_info_data) "
147+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
148+
(10, 10010, 1, 10, 2, 1710000010, "old", None, None),
149+
)
150+
conn.commit()
151+
finally:
152+
conn.close()
153+
154+
session_conn = sqlite3.connect(str(account_dir / "session.db"))
155+
try:
156+
session_conn.execute(
157+
"""
158+
CREATE TABLE SessionTable (
159+
username TEXT PRIMARY KEY,
160+
summary TEXT DEFAULT '',
161+
last_timestamp INTEGER DEFAULT 0,
162+
sort_timestamp INTEGER DEFAULT 0,
163+
last_msg_locald_id INTEGER DEFAULT 0,
164+
last_msg_type INTEGER DEFAULT 0,
165+
last_msg_sub_type INTEGER DEFAULT 0,
166+
last_msg_sender TEXT DEFAULT ''
167+
)
168+
"""
169+
)
170+
session_conn.commit()
171+
finally:
172+
session_conn.close()
173+
174+
def _fake_exec_query(_handle, *, kind, path, sql):
175+
self.assertEqual(kind, "message")
176+
self.assertTrue(str(path).endswith("message_0.db"))
177+
if "COUNT(1)" in sql:
178+
return [{"c": 2, "mx": 2}]
179+
raise AssertionError(f"Unexpected SQL: {sql}")
180+
181+
live_messages = [
182+
{
183+
"local_id": 11,
184+
"server_id": 10011,
185+
"local_type": 1,
186+
"sort_seq": 11,
187+
"real_sender_id": 2,
188+
"create_time": 1710000011,
189+
"message_content": "new message",
190+
"compress_content": None,
191+
"sender_username": username,
192+
}
193+
]
194+
195+
with (
196+
patch.object(
197+
chat_router,
198+
"_resolve_db_storage_message_paths",
199+
return_value=(Path(td) / "live_message_0.db", Path(td) / "message_resource.db"),
200+
),
201+
patch.object(chat_router, "_wcdb_exec_query", side_effect=_fake_exec_query),
202+
patch.object(chat_router, "_wcdb_get_messages", side_effect=[list(live_messages)]),
203+
patch.object(chat_router, "_best_effort_upsert_output_name2id_rows") as mock_upsert_name2id,
204+
):
205+
result = chat_router._sync_chat_realtime_messages_for_table(
206+
account_dir=account_dir,
207+
rt_conn=_DummyConn(),
208+
username=username,
209+
msg_db_path=msg_db_path,
210+
table_name=table_name,
211+
max_scan=50,
212+
backfill_limit=0,
213+
)
214+
215+
self.assertEqual(result.get("inserted"), 1)
216+
mock_upsert_name2id.assert_not_called()
217+
218+
conn = sqlite3.connect(str(msg_db_path))
219+
try:
220+
rows = conn.execute(
221+
f'SELECT local_id, server_id, real_sender_id, create_time, message_content FROM "{table_name}" ORDER BY local_id ASC'
222+
).fetchall()
223+
finally:
224+
conn.close()
225+
226+
self.assertEqual(
227+
rows,
228+
[
229+
(10, 10010, 2, 1710000010, "old"),
230+
(11, 10011, 2, 1710000011, "new message"),
231+
],
232+
)
233+
115234

116235
if __name__ == "__main__":
117236
unittest.main()

0 commit comments

Comments
 (0)