Skip to content

Commit ceae07d

Browse files
committed
inbox-summary #notests
1 parent c66d909 commit ceae07d

File tree

1 file changed

+195
-0
lines changed

1 file changed

+195
-0
lines changed

bin/inbox-summary.py

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
#!/usr/bin/env python
2+
3+
import dataclasses
4+
from collections.abc import Iterable
5+
6+
import click
7+
from sqlalchemy import and_, or_
8+
9+
from inbox.crispin import CrispinClient, writable_connection_pool
10+
from inbox.models.account import Account
11+
from inbox.models.backends.imap import ImapUid
12+
from inbox.models.folder import Folder
13+
from inbox.models.session import global_session_scope
14+
15+
16+
@dataclasses.dataclass
17+
class LocalAccount:
18+
id: int
19+
email: str
20+
provider: str
21+
sync_state: str
22+
23+
24+
def fetch_accounts(
25+
*, host: "str | None", account_id: "str | None"
26+
) -> "list[LocalAccount]":
27+
with global_session_scope() as db_session:
28+
accounts = db_session.query(Account).filter(Account.sync_state == "running")
29+
if host:
30+
process_identifier = f"{host}:0"
31+
accounts = accounts.filter(
32+
Account.sync_should_run,
33+
or_(
34+
and_(
35+
Account.desired_sync_host == process_identifier,
36+
Account.sync_host.is_(None),
37+
),
38+
and_(
39+
Account.desired_sync_host.is_(None),
40+
Account.sync_host == process_identifier,
41+
),
42+
and_(
43+
Account.desired_sync_host == process_identifier,
44+
Account.sync_host == process_identifier,
45+
),
46+
),
47+
)
48+
if account_id:
49+
accounts = accounts.filter(Account.id == account_id)
50+
51+
return [
52+
LocalAccount(
53+
id=account.id,
54+
email=account.email_address,
55+
provider=account.provider,
56+
sync_state=account.sync_state,
57+
)
58+
for account in accounts
59+
]
60+
61+
62+
@dataclasses.dataclass
63+
class ServerInfo:
64+
welcome: str
65+
capabilities: list[str]
66+
67+
68+
def get_server_info(crispin_client: CrispinClient, account: Account) -> ServerInfo:
69+
return ServerInfo(
70+
welcome=crispin_client.conn.welcome.decode(),
71+
capabilities=[
72+
capability.decode() for capability in crispin_client.conn.capabilities()
73+
],
74+
)
75+
76+
77+
@dataclasses.dataclass
78+
class RemoteFolder:
79+
name: str
80+
role: "str | None"
81+
uidnext: int
82+
exists: int
83+
84+
85+
def fetch_remote_folders(
86+
provider: str, crispin_client: CrispinClient
87+
) -> Iterable[RemoteFolder]:
88+
try:
89+
folder_names = crispin_client.folder_names()
90+
except Exception:
91+
return
92+
93+
for role, folders in folder_names.items():
94+
if provider == "gmail" and role not in ["all", "spam", "trash"]:
95+
continue
96+
97+
for folder in folders:
98+
try:
99+
result = crispin_client.select_folder(
100+
folder, lambda _account_id, _folder_name, select_info: select_info
101+
)
102+
except Exception:
103+
continue
104+
105+
yield RemoteFolder(
106+
name=folder,
107+
role=role,
108+
uidnext=result[b"UIDNEXT"],
109+
exists=result[b"EXISTS"],
110+
)
111+
112+
113+
@dataclasses.dataclass
114+
class LocalFolder:
115+
id: int
116+
name: str
117+
state: str
118+
uidnext: int
119+
exists: int
120+
121+
122+
def fetch_local_folders(account: LocalAccount) -> Iterable[LocalFolder]:
123+
with global_session_scope() as db_session:
124+
for folder in db_session.query(Folder).filter(Folder.account_id == account.id):
125+
exists = (
126+
db_session.query(ImapUid).filter(ImapUid.folder_id == folder.id).count()
127+
)
128+
uidnext = (
129+
(
130+
db_session.query(ImapUid.msg_uid)
131+
.filter(ImapUid.folder_id == folder.id)
132+
.order_by(ImapUid.msg_uid.desc())
133+
.limit(1)
134+
.scalar()
135+
)
136+
or 0
137+
) + 1
138+
yield LocalFolder(
139+
id=folder.id,
140+
name=folder.name,
141+
state=folder.imapsyncstatus.state,
142+
uidnext=uidnext,
143+
exists=exists,
144+
)
145+
146+
147+
@click.command()
148+
@click.option("--host", default=None)
149+
@click.option("--account-id", default=None)
150+
@click.option("--include-server-info", is_flag=True)
151+
def main(host: "str | None", account_id: "str | None", include_server_info: bool):
152+
accounts = fetch_accounts(host=host, account_id=account_id)
153+
total_remote_exists = 0
154+
total_local_exists = 0
155+
for account in accounts:
156+
print(account)
157+
158+
try:
159+
with writable_connection_pool(account.id).get() as crispin_client:
160+
if include_server_info:
161+
server_info = get_server_info(crispin_client, account)
162+
print("\t", server_info)
163+
print()
164+
165+
total_folder_remote_exists = 0
166+
for remote_folder in fetch_remote_folders(
167+
account.provider, crispin_client
168+
):
169+
print("\t", remote_folder)
170+
total_folder_remote_exists += remote_folder.exists
171+
total_remote_exists += remote_folder.exists
172+
print("\t Total remote EXISTS:", total_folder_remote_exists)
173+
print()
174+
175+
total_folder_local_exists = 0
176+
for local_folder in fetch_local_folders(account):
177+
print("\t", local_folder)
178+
total_folder_local_exists += local_folder.exists
179+
total_local_exists += local_folder.exists
180+
print("\t Total local EXISTS:", total_folder_local_exists)
181+
print(
182+
"\t Total difference:",
183+
total_folder_remote_exists - total_folder_local_exists,
184+
)
185+
print()
186+
except Exception as e:
187+
print("\t Exception opening the connection", e)
188+
print()
189+
190+
print("Total remote EXISTS:", total_remote_exists)
191+
print("Total local EXISTS:", total_local_exists)
192+
193+
194+
if __name__ == "__main__":
195+
main()

0 commit comments

Comments
 (0)