@@ -86,64 +86,128 @@ def fetch_remote_folders(
8686 provider : str , crispin_client : CrispinClient
8787) -> Iterable [RemoteFolder ]:
8888 try :
89- folder_names = crispin_client .folder_names ()
89+ folders = crispin_client .folders ()
9090 except Exception :
9191 return
9292
93- for role , folders in folder_names . items ( ):
94- if provider == "gmail" and role not in ["all" , "spam" , "trash" ]:
93+ for folder in sorted ( folders , key = lambda f : f . display_name ):
94+ if provider == "gmail" and folder . role not in ["all" , "spam" , "trash" ]:
9595 continue
9696
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" ],
97+ try :
98+ result = crispin_client .select_folder (
99+ folder .display_name ,
100+ lambda _account_id , _folder_name , select_info : select_info ,
110101 )
102+ except Exception :
103+ continue
104+
105+ yield RemoteFolder (
106+ name = folder .display_name ,
107+ role = folder .role ,
108+ uidnext = result [b"UIDNEXT" ],
109+ exists = result [b"EXISTS" ],
110+ )
111111
112112
113113@dataclasses .dataclass
114114class LocalFolder :
115115 id : int
116116 name : str
117117 state : str
118- uidnext : int
118+ uidmax : int
119119 exists : int
120120
121121
122122def fetch_local_folders (account : LocalAccount ) -> Iterable [LocalFolder ]:
123123 with global_session_scope () as db_session :
124- for folder in db_session .query (Folder ).filter (Folder .account_id == account .id ):
124+ for folder in (
125+ db_session .query (Folder )
126+ .filter (Folder .account_id == account .id )
127+ .order_by (Folder .name )
128+ ):
125129 exists = (
126130 db_session .query (ImapUid ).filter (ImapUid .folder_id == folder .id ).count ()
127131 )
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
132+ uidmax = (
133+ db_session .query (ImapUid .msg_uid )
134+ .filter (ImapUid .folder_id == folder .id )
135+ .order_by (ImapUid .msg_uid .desc ())
136+ .limit (1 )
137+ .scalar ()
138+ ) or 0
138139 yield LocalFolder (
139140 id = folder .id ,
140141 name = folder .name ,
141142 state = folder .imapsyncstatus .state ,
142- uidnext = uidnext ,
143+ uidmax = uidmax ,
143144 exists = exists ,
144145 )
145146
146147
148+ @dataclasses .dataclass
149+ class SummarizedList :
150+ value : list
151+ max_values : int = 10
152+
153+ def __repr__ (self ):
154+ if len (self .value ) <= self .max_values :
155+ return repr (self .value )
156+
157+ return f"[{ self .value [0 ]} , ... ,{ self .value [- 1 ]} len={ len (self .value )} ]"
158+
159+
160+ @dataclasses .dataclass
161+ class LocalFolderDiff :
162+ name : str
163+ uids_to_add : list [int ]
164+ uids_to_delete : list [int ]
165+
166+
167+ @dataclasses .dataclass
168+ class LocalFolderMissing :
169+ name : str
170+
171+
172+ def compare_local_and_remote (
173+ crispin_client : CrispinClient ,
174+ remote_folders : list [RemoteFolder ],
175+ local_folders : list [LocalFolder ],
176+ ):
177+ remote_folders_by_name = {folder .name : folder for folder in remote_folders }
178+ local_folders_by_name = {folder .name : folder for folder in local_folders }
179+
180+ for name , remote_folder in remote_folders_by_name .items ():
181+ local_folder = local_folders_by_name .get (name )
182+ if not local_folder :
183+ yield LocalFolderMissing (name = name )
184+
185+ if local_folder .exists == remote_folder .exists :
186+ continue
187+
188+ crispin_client .select_folder (
189+ local_folder .name ,
190+ lambda _account_id , _folder_name , select_info : select_info ,
191+ )
192+ remote_uids = set (crispin_client .all_uids ())
193+ with global_session_scope () as db_session :
194+ local_uids = set (
195+ uid
196+ for uid , in db_session .query (ImapUid .msg_uid ).filter (
197+ ImapUid .folder_id == local_folder .id
198+ )
199+ )
200+
201+ uids_to_add = remote_uids - local_uids
202+ uids_to_delete = local_uids - remote_uids
203+
204+ yield LocalFolderDiff (
205+ name = local_folder .name ,
206+ uids_to_add = SummarizedList (sorted (uids_to_add )),
207+ uids_to_delete = SummarizedList (sorted (uids_to_delete )),
208+ )
209+
210+
147211@click .command ()
148212@click .option ("--host" , default = None )
149213@click .option ("--account-id" , default = None )
@@ -163,18 +227,22 @@ def main(host: "str | None", account_id: "str | None", include_server_info: bool
163227 print ()
164228
165229 total_folder_remote_exists = 0
230+ remote_folders = []
166231 for remote_folder in fetch_remote_folders (
167232 account .provider , crispin_client
168233 ):
169234 print ("\t " , remote_folder )
235+ remote_folders .append (remote_folder )
170236 total_folder_remote_exists += remote_folder .exists
171237 total_remote_exists += remote_folder .exists
172238 print ("\t Total remote EXISTS:" , total_folder_remote_exists )
173239 print ()
174240
175241 total_folder_local_exists = 0
242+ local_folders = []
176243 for local_folder in fetch_local_folders (account ):
177244 print ("\t " , local_folder )
245+ local_folders .append (local_folder )
178246 total_folder_local_exists += local_folder .exists
179247 total_local_exists += local_folder .exists
180248 print ("\t Total local EXISTS:" , total_folder_local_exists )
@@ -183,6 +251,12 @@ def main(host: "str | None", account_id: "str | None", include_server_info: bool
183251 total_folder_remote_exists - total_folder_local_exists ,
184252 )
185253 print ()
254+
255+ for diff in compare_local_and_remote (
256+ crispin_client , remote_folders , local_folders
257+ ):
258+ print ("\t " , diff )
259+ print ()
186260 except Exception as e :
187261 print ("\t Exception opening the connection" , e )
188262 print ()
0 commit comments