Skip to content

Commit fb7eedd

Browse files
committed
fix: mark notes for redownload if they are corrupted in DB
1 parent e4f1d0d commit fb7eedd

File tree

2 files changed

+103
-4
lines changed

2 files changed

+103
-4
lines changed

evernote_backup/note_storage.py

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -295,26 +295,41 @@ def iter_notes(self, notebook_guid: str) -> Iterator[Note]:
295295
for note_guid in self._get_notes_by_notebook(notebook_guid):
296296
with self.db as con:
297297
cur = con.execute(
298-
"select raw_note"
298+
"select title, guid, raw_note"
299299
" from notes"
300300
" where guid=? and raw_note is not NULL",
301301
(note_guid,),
302302
)
303303

304304
row = cur.fetchone()
305305

306-
yield pickle.loads(lzma.decompress(row["raw_note"]))
306+
raw_note = self._get_raw_note(
307+
row["title"],
308+
row["guid"],
309+
row["raw_note"],
310+
)
311+
312+
if raw_note:
313+
yield raw_note
307314

308315
def iter_notes_trash(self) -> Iterator[Note]:
309316
with self.db as con:
310317
cur = con.execute(
311-
"select raw_note"
318+
"select title, guid, raw_note"
312319
" from notes"
313320
" where is_active=0 and raw_note is not NULL"
314321
" order by title COLLATE NOCASE",
315322
)
316323

317-
yield from (pickle.loads(lzma.decompress(row["raw_note"])) for row in cur)
324+
for row in cur:
325+
raw_note = self._get_raw_note(
326+
row["title"],
327+
row["guid"],
328+
row["raw_note"],
329+
)
330+
331+
if raw_note:
332+
yield raw_note
318333

319334
def get_notes_for_sync(self) -> Tuple[NoteForSync, ...]:
320335
with self.db as con:
@@ -375,6 +390,26 @@ def _get_notes_by_notebook(self, notebook_guid: str) -> List[str]:
375390

376391
return [r["guid"] for r in sorted_notes]
377392

393+
def _get_raw_note(self, note_title: str, note_guid: str, raw_note: bytes):
394+
try:
395+
return pickle.loads(lzma.decompress(raw_note))
396+
except Exception as e:
397+
if logger.getEffectiveLevel() == logging.DEBUG:
398+
logger.exception(f"Note '{note_title}' [{note_guid}] is corrupt: {e}")
399+
400+
logger.warning(
401+
f"Note '{note_title}' [{note_guid}] is corrupt, it will be re-downloaded during next sync"
402+
)
403+
404+
self._mark_note_for_redownload(note_guid)
405+
406+
def _mark_note_for_redownload(self, note_guid: str):
407+
with self.db as con:
408+
con.execute(
409+
"update notes set raw_note=NULL, is_active=NULL where guid=?",
410+
(note_guid,),
411+
)
412+
378413

379414
class ConfigStorage(SqliteStorage):
380415
def set_config_value(self, name: str, config_value: str) -> None:

tests/test_note_storage.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
from pathlib import Path
23

34
import pytest
@@ -341,6 +342,69 @@ def test_notes_trash(fake_storage):
341342
assert result_notes == expected_notes
342343

343344

345+
def test_notes_corrupt(fake_storage, caplog):
346+
test_notes = [
347+
Note(
348+
guid="id1",
349+
title="test",
350+
content="test",
351+
notebookGuid="notebook1",
352+
active=True,
353+
),
354+
Note(
355+
guid="id2",
356+
title="test",
357+
content="test",
358+
notebookGuid="notebook1",
359+
active=True,
360+
),
361+
Note(
362+
guid="id3",
363+
title="test",
364+
content="test",
365+
notebookGuid="notebook1",
366+
active=True,
367+
),
368+
]
369+
370+
expected_notes = [
371+
Note(
372+
guid="id1",
373+
title="test",
374+
content="test",
375+
notebookGuid="notebook1",
376+
active=True,
377+
),
378+
Note(
379+
guid="id2",
380+
title="test",
381+
content="test",
382+
notebookGuid="notebook1",
383+
active=True,
384+
),
385+
]
386+
387+
for note in test_notes:
388+
fake_storage.notes.add_note(note)
389+
390+
with fake_storage.db as con:
391+
con.execute("UPDATE notes SET raw_note=? WHERE guid=?", (b"123", "id3"))
392+
393+
with caplog.at_level(logging.DEBUG, logger="evernote_backup"):
394+
result_notes = list(fake_storage.notes.iter_notes("notebook1"))
395+
396+
result_notes_for_sync = fake_storage.notes.get_notes_for_sync()
397+
398+
assert result_notes == expected_notes
399+
assert len(result_notes_for_sync) == 1
400+
assert result_notes_for_sync[0].guid == "id3"
401+
assert "Traceback" in caplog.text
402+
assert (
403+
caplog.messages[1]
404+
== "Note 'test' [id3] is corrupt, it will be re-downloaded during next sync"
405+
)
406+
407+
344408
def test_get_notes_for_sync(fake_storage):
345409
test_notes = [
346410
Note(

0 commit comments

Comments
 (0)