Files
xian-favor/xian_favor/__pycache__/db.cpython-310.pyc

292 lines
29 KiB
Plaintext
Raw Normal View History

2026-04-14 17:27:37 +08:00
o
_<><5F>i<EFBFBD><69><00>@s<>dZddlZddlZddlZddlZddlmZmZddlmZm Z m
2026-04-14 17:27:37 +08:00
Z
m Z ddl m Z ddlmZmZmZej<12>ej<12>e<0F>d<08>ZGd d
<EFBFBD>d
<EFBFBD>Ze<16>ZdS) u数据库操作<E6938D>N)<02>datetime<6D> timedelta)<04>Optional<61>List<73>Dict<63>Any)<01>contextmanager<65>)<03> DATABASE_URL<52> TODO_STATUS<55>PRIORITY_LEVELS<4C>backupsc@s<>eZdZdZefdefdd<04>Zdd<06>Zedd<08><00>Z d d
2026-04-14 17:27:37 +08:00
<EFBFBD>Z
d<>dededededededededede ede de fdd<1D>Zde deeeeffdd <20>Z "d<>deded#ed$ed%e d&ed'ed(e d)e de eeeffd*d+<2B>Z d<>deded#ed$ed%e de f d,d-<2D>Zde de fd.d/<2F>Zde de fd0d1<64>Zd<>d(e d)e de eeeffd2d3<64>Zde fd4d5<64>Zde de fd6d7<64>Zde de fd8d9<64>Zde fd:d;<3B>Zde de fd<d=<3D>Zd<>de d%e de fd?d@<40>Zde de fdAdB<64>Z d<>dedededededededededede de fdCdD<64>ZdEe de fdFdG<64>Z d<>d(e d)e de eeeffdHdI<64>Z!de fdJdK<64>Z"dEe deeeeffdLdM<64>Z#dEe de fdNdO<64>Z$dEe dee fdPdQ<64>Z%d<>dSedTede fdUdV<64>Z&de eeeffdWdX<64>Z'dYe dSede fdZd[<5B>Z(d<>dYe dSede fd\d]<5D>Z)de de efd^d_<64>Z*de de efd`da<64>Z+d<>dbedSede fdcdd<64>Z,de eeeffdedf<64>Z-dge deeeeffdhdi<64>Z.d<>dge dbedSede fdjdk<64>Z/dge de fdldm<64>Z0d<30>de dbedne de fdodp<64>Z1de de eeeffdqdr<64>Z2deeeffdsdt<64>Z3deeeffdudv<64>Z4d<34>dwe deeeffdxdy<64>Z5de eeeffdzd{<7B>Z6d|ede fd}d~<7E>Z7d|ede fdd<7F><64>Z8d<38>eeeffd<>d<EFBFBD><64>Z9d|efd<>d<EFBFBD><64>Z:d<>ede fd<>d<EFBFBD><64>Z;d<>d<EFBFBD><64>Z<d S)<29><>DatabaseuSQLite数据库管理<E7AEA1>db_pathcCs||_d|_dS)NF)r<00> _initialized)<02>selfr<00>r<00>F/home/xian/.openclaw/workspace-coder/works/xian-favor/xian_favor/db.py<70>__init__s
zDatabase.__init__cCs|jrdS|<00><01>d|_dS)u确保数据库已初始化NT)r<00>_init_db)rrrr<00> _ensure_inits
2026-04-14 17:27:37 +08:00
zDatabase._ensure_initccsN<00>tj|jdd<02>}tj|_|<01>d<03>|<01>d<04>z
|VW|<01><06>dS|<01><06>w)u获取数据库连接g>@)<01>timeoutzPRAGMA journal_mode=WALzPRAGMA busy_timeout=30000N)<07>sqlite3<65>connectr<00>Row<6F> row_factory<72>execute<74>close)r<00>connrrr<00>get_conns<02>
2026-04-14 17:27:37 +08:00

zDatabase.get_connc Csn|<00><00><00><>}|<01><01>}|<02>d<01>|<02>d<02>|<02>d<03>|<02>d<04>|<02>d<05>|<02>d<06>|<02>d<07>|<02>d<08>|<02>d <09>|<02>d
<EFBFBD>|<02>d <0B>z|<02>d <0C>WntjyV|<02>d <0A>Ynw|<02>d<0E>z|<02>d<0F>Wntjyr|<02>d<10>Ynwz|<02>d<11>Wntjy<>|<02>d<12>Ynwz|<02>d<13>Wntjy<>|<02>d<14>Ynw|<01><05>Wd<00>dS1s<>wYdS)u初始化数据库表a<E8A1A8>
2026-04-14 17:27:37 +08:00
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL DEFAULT 'text',
title TEXT,
content TEXT,
url TEXT,
source TEXT,
status TEXT DEFAULT 'pending',
priority TEXT DEFAULT 'medium',
due_date TEXT,
note TEXT,
is_starred INTEGER DEFAULT 0,
views INTEGER DEFAULT 0,
is_deleted INTEGER DEFAULT 0,
deleted_at TEXT,
2026-04-14 17:27:37 +08:00
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
a
CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
color TEXT DEFAULT '#3498db',
created_at TEXT NOT NULL
)
a<>
CREATE TABLE IF NOT EXISTS item_tags (
item_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
PRIMARY KEY (item_id, tag_id),
FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
)
a 
CREATE TABLE IF NOT EXISTS emails (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
name TEXT,
created_at TEXT NOT NULL
)
a<>
CREATE TABLE IF NOT EXISTS drafts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL DEFAULT 'text',
title TEXT,
content TEXT,
url TEXT,
source TEXT,
status TEXT DEFAULT 'pending',
priority TEXT DEFAULT 'medium',
due_date TEXT,
note TEXT,
tags TEXT,
is_starred INTEGER DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
2026-04-14 17:27:37 +08:00
a<>
CREATE TABLE IF NOT EXISTS email_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
item_id INTEGER NOT NULL,
email TEXT NOT NULL,
sent_at TEXT NOT NULL,
success INTEGER DEFAULT 1,
FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE
)
z8CREATE INDEX IF NOT EXISTS idx_items_type ON items(type)z<CREATE INDEX IF NOT EXISTS idx_items_status ON items(status)zACREATE INDEX IF NOT EXISTS idx_items_created ON items(created_at)zCCREATE INDEX IF NOT EXISTS idx_item_tags_item ON item_tags(item_id)zACREATE INDEX IF NOT EXISTS idx_item_tags_tag ON item_tags(tag_id)z$SELECT is_starred FROM items LIMIT 1z9ALTER TABLE items ADD COLUMN is_starred INTEGER DEFAULT 0zACREATE INDEX IF NOT EXISTS idx_items_starred ON items(is_starred)zSELECT views FROM items LIMIT 1z4ALTER TABLE items ADD COLUMN views INTEGER DEFAULT 0z$SELECT is_deleted FROM items LIMIT 1z9ALTER TABLE items ADD COLUMN is_deleted INTEGER DEFAULT 0z$SELECT deleted_at FROM items LIMIT 1z,ALTER TABLE items ADD COLUMN deleted_at TEXTN)r<00>cursorrr<00>OperationalError<6F>commit<69>rrr rrrr,sH
2026-04-14 17:27:37 +08:00



2026-04-14 17:27:37 +08:00

2026-04-14 17:27:37 +08:00



<02>
<02><02><02>
"<22>zDatabase._init_db<64>textN<74>pending<6E>mediumF<6D>type<70>title<6C>content<6E>url<72>source<63>status<75>priority<74>due_date<74>note<74>tags<67>
is_starred<EFBFBD>returnc Cs<>|<00><00>t<01><02><00><03>} |dkr|tvrd}|tvrd}|<00><06><00>4} | <0A><07>}|<0E>d||||||||| | r3dnd| | f <0C>|j }|
rE|<00>
| ||
<EFBFBD>| <0A> <0B>|Wd<00>S1sUwYdS)u创建新条目<E69DA1>todor%r&z<>
INSERT INTO items (type, title, content, url, source, status, priority, due_date, note, is_starred, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
r rN) rr<00>now<6F> isoformatr r rr r<00> lastrowid<69>_add_tags_to_itemr")rr'r(r)r*r+r,r-r.r/r0r1r4rr <00>item_idrrr<00> create_item<65>s" 
"<04>$<24>zDatabase.create_itemr8cCs||<00><00><00>0}|<02><01>}|<03>d|f<01>|<03><03>}|s Wd<00>dSt|<04>}|<00>||<01>|d<|Wd<00>S1s7wYdS)u获取单个条目z SELECT * FROM items WHERE id = ?Nr0)rr r<00>fetchone<6E>dict<63>_get_item_tags)rr8rr <00>row<6F>itemrrr<00>get_item<65>s
<10>$<24>zDatabase.get_item<65>2r<00>tag<61>keyword<72>starred<65>sort_by<62>
sort_order<EFBFBD>limit<69>offsetc
Cs<>|<00><00><00><>}
|
<EFBFBD><01>} d} g} g}|<0E>d<02>|r$| d7} |<0E>d<04>| <0A>|<03>|r0|<0E>d<05>| <0A>|<01>|r<|<0E>d<06>| <0A>|<02>|durN|<0E>d<08>| <0A>|rKd nd
<EFBFBD>|rc|<0E>d <0B>d |<04>d <0C>}| <0A>|||g<03>|rn| d d<0E>|<0E>7} |dkrud}n |dkr|d}nd}|dks<>|dur<>dnd}|dkr<>d}n |dkr<>d}nd}|r<>| d|<10>d|<11>d<19>7} n| d7} | <0A>|| g<02>| <0B>| | <0A>g}| <0B><06>D]}t|<13>}|<00>|
|d<00>|d<|<12>|<14>q<>|Wd<00>S1s<>wYdS)ud列出条目
sort_by: created_at, updated_at
sort_order: desc, asc
z SELECT DISTINCT i.* FROM items izi.is_deleted = 0<>G JOIN item_tags it ON i.id = it.item_id JOIN tags t ON it.tag_id = t.id<69>
t.name = ?<3F>
i.type = ?<3F> i.status = ?N<>i.is_starred = ?r r<00>5(i.title LIKE ? OR i.content LIKE ? OR i.note LIKE ?)<29>%<25> WHERE <20> AND <20>
updated_atz i.updated_at<61>
created_atz i.created_at<61>asc<73>DESC<53>ASC<53>descz
ORDER BY <20> z LIMIT ? OFFSET ?z? ORDER BY i.is_starred DESC, i.created_at DESC LIMIT ? OFFSET ?<3F>idr0) rr <00>append<6E>extend<6E>joinr<00>fetchallr;r<)rr'r,rArBrCrDrErFrGrr <00>query<72>params<6D>
conditions<EFBFBD>keyword_pattern<72> order_field<6C> order_dir<69>itemsr=r>rrr<00>
list_items<EFBFBD>s`


2026-04-14 17:27:37 +08:00


2026-04-14 17:27:37 +08:00





    $<24>zDatabase.list_itemsc Cs |<00><00><00>x}|<06><01>}d}g} g}
|r|d7}|
<EFBFBD>d<03>| <09>|<03>|r+|
<EFBFBD>d<04>| <09>|<01>|r7|
<EFBFBD>d<05>| <09>|<02>|durI|
<EFBFBD>d<07>| <09>|rFdnd <09>|r^|
<EFBFBD>d
<EFBFBD>d |<04>d <0B>} | <09>| | | g<03>|
ri|d d <0A>|
<EFBFBD>7}|<07>|| <09>|<07><06>dWd<00>S1swYdS)u!计算符合条件的条目总数z1SELECT COUNT(DISTINCT i.id) as count FROM items irHrIrJrKNrLr rrMrNrOrP<00>count)rr rYrZr[rr:) rr'r,rArBrCrr r]r^r_r`rrr<00> count_items$s6


2026-04-14 17:27:37 +08:00






  
$<24>zDatabase.count_itemsc  sgd<01><01><00>fdd<03>|<02><00>D<00>}|sd|vrdSt<01><02><00><03>}|<00><04><00>d}|<05><05>}|<06>d|f<01>|<06><07>s: Wd<00>dS|r`d<08>d d
<EFBFBD>|<03> <09>D<00><01>}|d 7}t
|<03> <0B><00>||g}|<06>d |<07>d <0A>|<08>d|vrx|<06>d|f<01>|drx|<00> |||d<00>|<05> <0A> Wd<00>dS1s<>wYdS)u 更新条目)
r'r(r)r*r+r,r-r.r/r1c<00>i|] \}}|<01>vr||<02>qSrr<00><03>.0<EFBFBD>k<>v<><01>allowed_fieldsrr<00>
<dictcomp>N<00>z(Database.update_item.<locals>.<dictcomp>r0Fz!SELECT id FROM items WHERE id = ?N<>, cs<00><00>|]}|<01>d<00>VqdS<00>z = ?Nr<00>rirjrrr<00> <genexpr>_<00><02>z'Database.update_item.<locals>.<genexpr><3E>, updated_at = ?zUPDATE items SET <20> WHERE id = ?<3F>'DELETE FROM item_tags WHERE item_id = ?T)rcrr4r5rr rr:r[<00>keys<79>list<73>valuesr7r") rr8<00>kwargs<67> update_fieldsr4rr <00>
set_clauser{rrlr<00> update_itemKs.  
<10>$<24>zDatabase.update_itemcCsb|<00><00><00>#}|<02><01>}t<02><03><00><04>}|<03>d||f<02>|<02><06>|jdkWd<00>S1s*wYdS)u$删除条目移动到回收站z<UPDATE items SET is_deleted = 1, deleted_at = ? WHERE id = ?rN<>rr rr4r5rr"<00>rowcount)rr8rr r4rrr<00> delete_itemns
 $<24>zDatabase.delete_itemcCs<>|<00><00><00>2}|<03><01>}|<04>d||f<02>g}|<04><03>D]}t|<06>}|<00>||d<00>|d<|<05>|<07>q|Wd<00>S1s9wYdS)u列出回收站数据zRSELECT * FROM items WHERE is_deleted = 1 ORDER BY deleted_at DESC LIMIT ? OFFSET ?rXr0N)rr rr\r;r<rY)rrFrGrr rcr=r>rrr<00>
list_trashws
  $<24>zDatabase.list_trashcC<00>J|<00><00><00>}|<01><01>}|<02>d<01>|<02><03>dWd<00>S1swYdS)u计算回收站数据总数z8SELECT COUNT(*) as count FROM items WHERE is_deleted = 1reN<>rr rr:r#rrr<00> count_trash<73><00>


$<24>zDatabase.count_trashcC<00>T|<00><00><00>}|<02><01>}|<03>d|f<01>|<02><03>|jdkWd<00>S1s#wYdS)u从回收站恢复数据z?UPDATE items SET is_deleted = 0, deleted_at = NULL WHERE id = ?rN<>rr rr"r<><00>rr8rr rrr<00> restore_item<65><00>
$<24>zDatabase.restore_itemcCsp|<00><00><00>*}|<02><01>}|<03>d|f<01>|<03>d|f<01>|<03>d|f<01>|<02><03>|jdkWd<00>S1s1wYdS)u-彻底删除数据从数据库中删除rx<00>(DELETE FROM email_logs WHERE item_id = ?zDELETE FROM items WHERE id = ?rNr<4E>r<>rrr<00>delete_permanently<6C>s
$<24>zDatabase.delete_permanentlycCs<>|<00><00><00>;}|<01><01>}|<02>d<01>dd<03>|<02><03>D<00>}|D]}|<02>d|f<01>|<02>d|f<01>q|<02>d<06>|j}|<01><05>|Wd<00>S1sBwYdS)u清空回收站z)SELECT id FROM items WHERE is_deleted = 1cS<00>g|]}|d<00>qS)rXr<00>rir=rrr<00>
<listcomp><3E><00>z(Database.empty_trash.<locals>.<listcomp>rxr<>z&DELETE FROM items WHERE is_deleted = 1N)rr rr\r<>r")rrr <00>idsr8<00> deleted_countrrr<00> empty_trash<73>s


$<24>zDatabase.empty_trashcCs<>|<00><00><00>?}|<02><01>}|<03>d|f<01>|<03><03>}|s Wd<00>dS|dr%dnd}t<04><05><00><06>}|<03>d|||f<03>|<02><07> Wd<00>dS1sFwYdS) u切换重点关注状态z)SELECT is_starred FROM items WHERE id = ?NFr1rr <00><UPDATE items SET is_starred = ?, updated_at = ? WHERE id = ?T)rr rr:rr4r5r")rr8rr r=<00>
new_statusr4rrr<00> toggle_star<61>s
<10> $<24>zDatabase.toggle_starTcCsl|<00><00><00>(}|<03><01>}t<02><03><00><04>}|<04>d|rdnd||f<03>|<03><06>|jdkWd<00>S1s/wYdS)u设置重点关注状态r<E68081>r rNr<4E>)rr8rCrr r4rrr<00>set_star<61>s
 $<24>zDatabase.set_starcCr<>)u增加阅读数z/UPDATE items SET views = views + 1 WHERE id = ?rNr<4E>r<>rrr<00>increment_views<77>r<>zDatabase.increment_viewsc Cs<>|<00><00>t<01><02><00><03>} |<00><04><00>,} | <0A><05>}|<0E>d||||||||| |
| r$dnd| | f <0A>|j}| <0A><08>|Wd<00>S1s=wYdS)u 保存草稿z<E7A8BF>
INSERT INTO drafts (type, title, content, url, source, status, priority, due_date, note, tags, is_starred, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
r rN) rrr4r5rr rr6r")rr'r(r)r*r+r,r-r.r/r0r1r4rr <00>draft_idrrr<00>
save_draft<EFBFBD>s 
$<04>$<24>zDatabase.save_draftr<74>c  s<>gd<01><01><00>fdd<03>|<02><00>D<00>}|sdSt<01><02><00><03>}|<00><04><00>J}|<05><05>}|<06>d|f<01>|<06><07>s6 Wd<00>dSd<07>dd <09>|<03> <09>D<00><01>}|d
7}t
|<03> <0B><00>||g}|<06>d |<07>d <0C>|<08>|<05> <0C> Wd<00>d S1sjwYdS)u 更新草稿<E88D89> r'r(r)r*r+r,r-r.r/r0r1crgrrrhrlrrrn<00>roz)Database.update_draft.<locals>.<dictcomp>Fz"SELECT id FROM drafts WHERE id = ?Nrpcsrqrrrrsrrrrt<00>ruz(Database.update_draft.<locals>.<genexpr>rvzUPDATE drafts SET rwT) rcrr4r5rr rr:r[ryrzr{r") rr<>r|r}r4rr r~r{rrlr<00> update_draft<66>s$ 
<10>$<24>zDatabase.update_draftcCsV|<00><00><00>}|<03><01>}|<04>d||f<02>dd<03>|<04><03>D<00>Wd<00>S1s$wYdS)u 列出草稿z>SELECT * FROM drafts ORDER BY updated_at DESC LIMIT ? OFFSET ?cS<00>g|]}t|<01><01>qSr<00>r;r<>rrrr<>r<>z(Database.list_drafts.<locals>.<listcomp>N<>rr rr\)rrFrGrr rrr<00> list_drafts<74>s
$<24>zDatabase.list_draftscCr<>)u计算草稿总数z$SELECT COUNT(*) as count FROM draftsreNr<4E>r#rrr<00> count_draftsr<>zDatabase.count_draftscC<00>Z|<00><00><00>}|<02><01>}|<03>d|f<01>|<03><03>}|rt|<04>ndWd<00>S1s&wYdS)u获取单个草稿z!SELECT * FROM drafts WHERE id = ?N<>rr rr:r;)rr<>rr r=rrr<00> get_draft <00>
$<24>zDatabase.get_draftcCr<>)u 删除草稿zDELETE FROM drafts WHERE id = ?rNr<4E>)rr<>rr rrr<00> delete_draftr<>zDatabase.delete_draftc Cs<>|<00>|<01>}|s dS|dr|d<00>d<03>ng}dd<05>|D<00>}|j|d|d|d|d |d
|d |d |d |d||dd<10> }|rH|<00>|<01>|S)u将草稿转为正式条目Nr0<00>,cSsg|]
}|<01><00>r|<01><00><00>qSr)<01>strip)ri<00>trrrr<>$<00>z*Database.draft_to_item.<locals>.<listcomp>r'r(r)r*r+r,r-r.r/r1r<>)r<><00>splitr9r<>)rr<><00>draft<66> tags_listr8rrr<00> draft_to_items*
<06>
zDatabase.draft_to_item<65>#3498db<64>name<6D>colorc C<00><>t<00><01><00><02>}|<00><03><00><}|<04><04>}z|<05>d|||f<03>|<04><06>|jWWd<00>Stj yE|<05>d|f<01>|<05>
<EFBFBD>dYWd<00>Sw1sIwYdS)u 创建标签z;INSERT INTO tags (name, color, created_at) VALUES (?, ?, ?)N<>"SELECT id FROM tags WHERE name = ?rX<00> rr4r5rr rr"r6r<00>IntegrityErrorr:)rr<>r<>r4rr rrr<00>
create_tag<<00> 
2026-04-14 17:27:37 +08:00
<04><10><10><14>zDatabase.create_tagcC<00>P|<00><00><00>}|<01><01>}|<02>d<01>dd<03>|<02><03>D<00>Wd<00>S1s!wYdS)u列出所有标签z<E7ADBE>
SELECT t.*, COUNT(it.item_id) as item_count
FROM tags t
LEFT JOIN item_tags it ON t.id = it.tag_id
GROUP BY t.id
ORDER BY t.name
cSr<>rr<>r<>rrrr<>Vr<>z&Database.list_tags.<locals>.<listcomp>Nr<4E>r#rrr<00> list_tagsKs
2026-04-14 17:27:37 +08:00

$<24>zDatabase.list_tags<67>tag_idcCs<>|<00><00><00>2}|<03><01>}|<04>d||f<02>|<04><03>r Wd<00>dS|<04>d||f<02>|<03><04>|jdkWd<00>S1s9wYdS)u更新标签名称z.SELECT id FROM tags WHERE name = ? AND id != ?NFz%UPDATE tags SET name = ? WHERE id = ?r)rr rr:r"r<><00>rr<>r<>rr rrr<00>
update_tagXs
<10>$<24>zDatabase.update_tagcCsl|<00><00><00>(}|<03><01>}|r|<04>d|f<01>n |r|<04>d|f<01>|<03><03>|jdkWd<00>S1s/wYdS)u 删除标签zDELETE FROM tags WHERE name = ?zDELETE FROM tags WHERE id = ?rNr<4E>r<>rrr<00>
delete_tagds
$<24>zDatabase.delete_tagc Csz|<01><00>}|D]4}|<05><01>}|sq|<04>d|f<01>|<04><03>}|r!|d}nt<04><05><00><06>}|<04>d||f<02>|j}|<04>d||f<02>qdS)u为条目添加标签r<E7ADBE>rXzCINSERT INTO tags (name, color, created_at) VALUES (?, '#3498db', ?)z?INSERT OR IGNORE INTO item_tags (item_id, tag_id) VALUES (?, ?)N)r r<>rr:rr4r5r6) rrr8r0r <00>tag_namer=r<>r4rrrr7qs$
2026-04-14 17:27:37 +08:00
 <04><06><04>zDatabase._add_tags_to_itemcCs(|<01><00>}|<03>d|f<01>dd<03>|<03><02>D<00>S)u获取条目的标签z<E7ADBE>
SELECT t.name FROM tags t
JOIN item_tags it ON t.id = it.tag_id
WHERE it.item_id = ?
ORDER BY t.name
cSr<><00>r<>rr<>rrrr<><00>r<>z+Database._get_item_tags.<locals>.<listcomp>)r rr\)rrr8r rrrr<<00>s
<04>zDatabase._get_item_tags<67>emailc Cr<>)u 创建邮箱z=INSERT INTO emails (email, name, created_at) VALUES (?, ?, ?)Nz%SELECT id FROM emails WHERE email = ?rXr<>)rr<>r<>r4rr rrr<00> create_email<69>r<>zDatabase.create_emailcCr<>)u列出所有邮箱z-SELECT * FROM emails ORDER BY created_at DESCcSr<>rr<>r<>rrrr<><00>r<>z(Database.list_emails.<locals>.<listcomp>Nr<4E>r#rrr<00> list_emails<6C>s
2026-04-14 17:27:37 +08:00

$<24>zDatabase.list_emails<6C>email_idcCr<>)u获取单个邮箱z!SELECT * FROM emails WHERE id = ?Nr<4E>)rr<>rr r=rrr<00> get_email<69>r<>zDatabase.get_emailcCs<>t<00><01><00><02>}|<00><03><00>O}|<05><04>}|r&|<06>d||f<02>|<06><06>r& Wd<00>dS|r4|r4|<06>d|||f<03>n|r?|<06>d||f<02>n
|rI|<06>d||f<02>|<05><07>|jdkWd<00>S1s\wYdS)u 更新邮箱z1SELECT id FROM emails WHERE email = ? AND id != ?NFz2UPDATE emails SET email = ?, name = ? WHERE id = ?z(UPDATE emails SET email = ? WHERE id = ?z'UPDATE emails SET name = ? WHERE id = ?r) rr4r5rr rr:r"r<>)rr<>r<>r<>r4rr rrr<00> update_email<69>s" 
<10>$<24>zDatabase.update_emailcCr<>)u 删除邮箱zDELETE FROM emails WHERE id = ?rNr<4E>)rr<>rr rrr<00> delete_email<69>r<>zDatabase.delete_email<69>successcCsjt<00><01><00><02>}|<00><03><00>!}|<05><04>}|<06>d||||rdndf<04>|<05><06>|jWd<00>S1s.wYdS)u记录邮件发送z{
2026-04-14 17:27:37 +08:00
INSERT INTO email_logs (item_id, email, sent_at, success)
VALUES (?, ?, ?, ?)
r rN)rr4r5rr rr"r6)rr8r<>r<>r4rr rrr<00>log_email_send<6E>s 
2026-04-14 17:27:37 +08:00
<04>$<24>zDatabase.log_email_sendcCsT|<00><00><00>}|<02><01>}|<03>d|f<01>dd<03>|<03><03>D<00>Wd<00>S1s#wYdS)u!获取收藏的邮件发送记录z<E5BD95>
SELECT * FROM email_logs
WHERE item_id = ?
ORDER BY sent_at DESC
cSr<>rr<>r<>rrrr<><00>r<>z+Database.get_email_logs.<locals>.<listcomp>Nr<4E>r<>rrr<00>get_email_logs<67>s
2026-04-14 17:27:37 +08:00
<04>$<24>zDatabase.get_email_logscCs<>|<00><00>|<00><01><00>J}|<01><02>}i}|<02>d<01>|<02><04>d|d<|<02>d<04>dd<06>|<02><05>D<00>|d<|<02>d<08>d d<06>|<02><05>D<00>|d
<|<02>d <0B>|<02><04>d|d <|Wd <00>S1sUwYd S)u获取统计信息z#SELECT COUNT(*) as count FROM itemsre<00>totalz7SELECT type, COUNT(*) as count FROM items GROUP BY typecS<00>i|] }|d|d<00>qS)r'rerr<>rrrrn<00><00>z"Database.stats.<locals>.<dictcomp><3E>by_typezOSELECT status, COUNT(*) as count FROM items WHERE type = 'todo' GROUP BY statuscSr<>)r,rerr<>rrrrn<00>r<><00> todo_statusz"SELECT COUNT(*) as count FROM tagsr0N)rrr rr:r\)rrr <00>statsrrrr<><00>s
2026-04-14 17:27:37 +08:00




$<24>zDatabase.statsc Cs<>|<00><00>|<00><01><00><>}|<01><02>}t<03><04>}gggd<01>}|<02>d<02>|<02><06>D]<5D>}t|<05>}|<00>||d<00>|d<z<>|d}d|vrDt<03> |dd<08>d <09>}nt
|<07>d
krWt<03> |d <0B>j d d d d<0E>}n t<03> |<07> d<0F>dd<11>}||} | <09> <0A>dkr<>tt| <09> <0A>d<00><01>}
|
|d<|d<00>|<06>n-| <09> <0A>dkr<>|<08><11>|<03><11>kr<>|d<00>|<06>n|d<00>|<06>n|<08><11>|<03><11>kr<>|d<00>|<06>Wq ttfy<>} zWYd} ~ q d} ~ wwt
|d<00>t
|d<00>t
|d<00>|d<|Wd<00>S1s<>wYdS)u6获取提醒信息:即将到期和已过期的待办)<03>overdue<75> due_today<61>due_soonz<6E>
SELECT * FROM items
WHERE type = 'todo'
AND status != 'completed'
AND due_date IS NOT NULL
AND due_date != ''
ORDER BY due_date ASC
rXr0r.<00>TN<54>z%Y-%m-%dT%H:%M<>
z%Y-%m-%d<><00>;)<03>hour<75>minute<74>second<6E>.rz%Y-%m-%dT%H:%M:%Si<53>Q<00> days_overduer<65>r<>r<>r<>)rrr rr4rr\r;r<<00>strptime<6D>len<65>replacer<65><00> total_seconds<64>abs<62>intrY<00>date<74>
ValueError<EFBFBD>AttributeError) rrr r4<00> remindersr=r><00> due_date_strr.<00> time_leftr<74><00>errr<00> get_reminderssL
<06>
    <04> <08><02>($<24>zDatabase.get_reminders<72>manualcCs<>ddl}|jtdd<04>t<03><04>}|<03>d<05>}|r|d7}|j<06>t|<04>d<07><02>}t<08> |j
|<05>|||j<06> |<05>|<03> <0C>||j dkd <09>}|<00>|<06>|<00><0F>|S)
u创建数据库备份rNT)<01>exist_okz%Y-%m-%d_%H%M%S<>_manual<61>.dbr <00>r<><00>path<74>sizerRr<><00>is_first_of_month)<10>os<6F>makedirs<72>
BACKUP_DIRrr4<00>strftimer<65>r[<00>shutil<69>copy2r<00>getsizer5<00>day<61>_save_backup_meta<74>_cleanup_old_backups)rr<>r<>r4<00> backup_name<6D> backup_path<74> backup_inforrr<00> create_backupFs$

<06>
zDatabase.create_backupc Cs<>ddl}|j<01>t<03>s gS|j<01>td<03>}|j<01>|<02>r4t|d<04><02> }t<06>|<03>}Wd<00>n1s.wYn8g}|<01>t<03>D]0}|<03> d<05>rk|j<01>t|<03>}|<04>
|<03> dd<06>||j<01> |<05>t <0A>|j<01>|<05><01><01><10>d|v|<00>|<03>d<08><06>q;|jd d
<EFBFBD>d d <0C>|S) u列出所有备份rN<>backup_meta.json<6F>rr<72><00>r<>r<>cSs|dS)NrRr)<01>xrrr<00><lambda><3E>sz'Database.list_backups.<locals>.<lambda>T)<02>key<65>reverse)r<>r<><00>existsr<73>r[<00>open<65>json<6F>load<61>listdir<69>endswithrYr<>r<>r<00> fromtimestamp<6D>getmtimer5<00>_is_first_of_month_filename<6D>sort)rr<><00> meta_path<74>fr r<>rrr<00> list_backupshs0    <1C><02>


<08><02>
zDatabase.list_backupsr<73>cCsVddl}|j<01>t|<01>d<03><02>}|j<01>|<03>sdS|jd}t<06>|j|<04>t<06>||j<05>dS)u 恢复备份rNr<4E>Fz.before_restoreT)r<>r<>r[r<>r
rr<>r<>)rr<>r<>r<00>current_backuprrr<00>restore_backup<75>s 
zDatabase.restore_backupcCsDddl}|j<01>t|<01>d<03><02>}|j<01>|<03>sdS|<02>|<03>|<00>|<01>dS)u 删除备份rNr<4E>FT)r<>r<>r[r<>r
<00>remove<76>_remove_backup_meta)rr<>r<>rrrr<00> delete_backup<75>s 

zDatabase.delete_backuprcCs<>ddl}|j<01>td<03>}g}|j<01>|<03>r-t|d<04><02> }t<06>|<05>}Wd<00>n1s(wY|<04>|<01>t|d<05><02>}tj ||dd<07>Wd<00>dS1sKwYdS)u保存备份元数据rNrr<00>w<><00><01>indent)
r<EFBFBD>r<>r[r<>r
r r r rY<00>dump)rrr<>rr rrrrr<><00>s   <1C>
 "<22>zDatabase._save_backup_metacs<>ddl}|j<01>td<03>}|j<01>|<03>sdSt|d<04><02> }t<06>|<04>}Wd<00>n1s(wY<00>fdd<06>|D<00>}t|d<07><02>}tj||dd <09>Wd<00>dS1sOwYdS)
u从元数据中删除备份rNrrcsg|]
}|d<00>kr|<01>qSr<>r<00>ri<00>b<>r<>rrr<><00>r<>z0Database._remove_backup_meta.<locals>.<listcomp>rrr) r<>r<>r[r<>r
r r r r )rr<>r<>rrr rr#rr<00>s   <1C> "<22>zDatabase._remove_backup_meta<74>filenamecCs8z|<01>d<01>d}t|<02>d<03>d<00>}|dkWSYdS)u'判断是否是每月第一天的备份<E5A487>_r<00>-rr F)r<>r<>)rr$<00> date_partr<74>rrrr<00>s 
z$Database._is_first_of_month_filenamec Cs<>ddl}|<00><01>}t<02><03>}g}|D]O}t<02>|d<00>}||j}|dr@dd<06>|D<00>}|<08>|<05>dkr8|<04>|d<00>q|<00>|d <00>q|d
krL|<04>|d<00>q|d rX|<04>|d<00>q|<00>|d <00>qdS) u/清理旧备份保留30天 + 每月第一天rNrRr<>cSsg|]}|dr|<01>qS)r<>rr!rrrr<><00>sz1Database._cleanup_old_backups.<locals>.<listcomp>r<>r<>r<><00>r<>) r<>rrr4<00> fromisoformat<61>days<79>indexrYr) rr<>r r4<00>
keep_paths<EFBFBD>backup<75> backup_date<74>days_old<6C>manual_backupsrrrr<><00>s$
<04>zDatabase._cleanup_old_backups) r$NNNNr%r&NNNF) NNNNNNNr@r)NNNNN)r@r)T)r<>)NN)N)F)=<3D>__name__<5F>
__module__<EFBFBD> __qualname__<5F>__doc__r
<00>strrrrrrr<00>boolr<6C>r9rrrr?rdrfrr<>r<>r<>r<>r<>r<>r<>r<>r<>r<>r<>r<>r<>r<>r<>r<>r<>r<>r<>r<>r7r<r<>r<>r<>r<>r<>r<>r<>r<>r<>rrrrr<>rrr<>rrrrrs<>
 ~<10><02><02><02><02><02><02><02><02>
<EFBFBD> <10><02><02><02><02><02><02>
<EFBFBD>P<10><02><02>
<EFBFBD>'#$    
<10><02><02><02><02><02><02><02><02>
<EFBFBD>$     
  D" 
r)r4rr r<>r<>rr<00>typingrrrr<00>
contextlibr<00>configr
r r r<>r[<00>dirnamer<65>r<00>dbrrrr<00><module>s& 
n