|
1 // Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies). |
|
2 // All rights reserved. |
|
3 // This component and the accompanying materials are made available |
|
4 // under the terms of "Eclipse Public License v1.0" |
|
5 // which accompanies this distribution, and is available |
|
6 // at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
7 // |
|
8 // Initial Contributors: |
|
9 // Nokia Corporation - initial contribution. |
|
10 // |
|
11 // Contributors: |
|
12 // |
|
13 // Description: |
|
14 // IMAP4 folder list synchronise |
|
15 // This class deals with keeping the local mirror folder tree in sync with the |
|
16 // remote server's directory structure. |
|
17 // 980914 - Modifications to local tree traverse so that it will not |
|
18 // look for children below message level: previously, it was |
|
19 // picking up attachment folders in messages! |
|
20 // 990304 - Orphaned folder check no longer orphans the inbox: we |
|
21 // basically never touch it. The problem with orphaning it |
|
22 // comes about with servers which have a namespace (ie |
|
23 // folder path) of INBOX, which means we never "see" the |
|
24 // actual inbox as we are inside it. We still know it |
|
25 // exists, though. |
|
26 // 990502 - Was calling CMsgActive::Cancel() as opposed to DoCancel(). |
|
27 // |
|
28 // |
|
29 |
|
30 #include <e32base.h> |
|
31 #include <e32cons.h> |
|
32 #include <mentact.h> |
|
33 #include "fldsync.h" |
|
34 #include "impspan.h" |
|
35 #include "imapsess.h" |
|
36 |
|
37 #ifdef _DEBUG |
|
38 #define LOG_COMMANDS(a) a |
|
39 #define DBG(a) a |
|
40 #define PRINTING |
|
41 #else |
|
42 #define LOG_COMMANDS(a) |
|
43 #define DBG(a) |
|
44 #undef PRINTING |
|
45 #endif |
|
46 |
|
47 // Priority of MsgActive object |
|
48 const TInt EFolderSyncPriority=1; |
|
49 |
|
50 |
|
51 // Debugging of folder tree traversal |
|
52 #undef DEBUG_FOLDERSYNC |
|
53 |
|
54 CImImap4FolderSync::CImImap4FolderSync():CMsgActive(EFolderSyncPriority) |
|
55 { |
|
56 __DECLARE_NAME(_S("CImImap4FolderSync")); |
|
57 } |
|
58 |
|
59 CImImap4FolderSync::~CImImap4FolderSync() |
|
60 { |
|
61 // Global bits |
|
62 delete iFolderContents; |
|
63 |
|
64 if (iFolderList) |
|
65 iFolderList->ResetAndDestroy(); |
|
66 delete iFolderList; |
|
67 |
|
68 // Get rid of bits at each level |
|
69 for(TInt a=0;a<KFolderDepth;a++) |
|
70 delete iFolderIds[a]; |
|
71 |
|
72 // Delete local folder list |
|
73 delete iLocalFolders; |
|
74 } |
|
75 |
|
76 CImImap4FolderSync* CImImap4FolderSync::NewLC(CImImap4Session *aSession) |
|
77 { |
|
78 CImImap4FolderSync* self=new (ELeave) CImImap4FolderSync(); |
|
79 CleanupStack::PushL(self); |
|
80 |
|
81 // Non-trivial constructor |
|
82 self->ConstructL(aSession); |
|
83 return self; |
|
84 } |
|
85 |
|
86 CImImap4FolderSync* CImImap4FolderSync::NewL(CImImap4Session *aSession) |
|
87 { |
|
88 CImImap4FolderSync* self=NewLC(aSession); |
|
89 CleanupStack::Pop(); |
|
90 return self; |
|
91 } |
|
92 |
|
93 // The non-trivial constructor |
|
94 void CImImap4FolderSync::ConstructL(CImImap4Session *aSession) |
|
95 { |
|
96 // Save session |
|
97 iSession=aSession; |
|
98 |
|
99 // We're an active object... |
|
100 CActiveScheduler::Add(this); |
|
101 |
|
102 // One-off bits |
|
103 iFolderList=new (ELeave) CArrayPtrFlat<CImImap4DirStruct>(8); |
|
104 iFolderContents=new (ELeave) CMsvEntrySelection; |
|
105 |
|
106 // ...and per-level bits |
|
107 for(TInt a=0;a<KFolderDepth;a++) |
|
108 iFolderIds[a]=new (ELeave) CArrayFixFlat<TMsvId>(8); |
|
109 |
|
110 // Local folder list |
|
111 iLocalFolders=new (ELeave) CArrayFixFlat<TMsvId>(16); |
|
112 } |
|
113 |
|
114 // Do setentry, leave if there is an error |
|
115 void CImImap4FolderSync::SetEntryL(const TMsvId aId) |
|
116 { |
|
117 User::LeaveIfError(iEntry->SetEntry(aId)); |
|
118 } |
|
119 |
|
120 // Change entry, leave if error |
|
121 void CImImap4FolderSync::ChangeEntryL(const TMsvEntry& aEntry) |
|
122 { |
|
123 User::LeaveIfError(iEntry->ChangeEntry(aEntry)); |
|
124 } |
|
125 |
|
126 // Change entry in bulk mode (i.e. no index file commit), leave if error |
|
127 void CImImap4FolderSync::ChangeEntryBulkL(const TMsvEntry& aEntry) |
|
128 { |
|
129 User::LeaveIfError(iEntry->ChangeEntryBulk(aEntry)); |
|
130 } |
|
131 // Get children, leave if error |
|
132 void CImImap4FolderSync::GetChildrenL(CMsvEntrySelection& aSelection) |
|
133 { |
|
134 User::LeaveIfError(iEntry->GetChildren(aSelection)); |
|
135 } |
|
136 |
|
137 // Set the entry to use to talk to the server |
|
138 void CImImap4FolderSync::SetEntry(CMsvServerEntry* aEntry) |
|
139 { |
|
140 // Save it |
|
141 iEntry=aEntry; |
|
142 } |
|
143 |
|
144 // Build list of local folders |
|
145 void CImImap4FolderSync::BuildLocalL(const TMsvId aFolder, const TBool aDoThisOne) |
|
146 { |
|
147 // Select it |
|
148 SetEntryL(aFolder); |
|
149 TMsvEmailEntry entry=iEntry->Entry(); |
|
150 |
|
151 // If we're a folder, set the flag |
|
152 if (entry.iType==KUidMsvFolderEntry && aDoThisOne) |
|
153 { |
|
154 // Add to list |
|
155 iLocalFolders->AppendL(iEntry->Entry().Id()); |
|
156 } |
|
157 |
|
158 // If current entry is a message, don't look for children |
|
159 if (entry.iType==KUidMsvMessageEntry) |
|
160 return; |
|
161 |
|
162 // Any children? |
|
163 CMsvEntrySelection *children=new (ELeave) CMsvEntrySelection; |
|
164 CleanupStack::PushL(children); |
|
165 GetChildrenL(*children); |
|
166 if (children->Count()) |
|
167 { |
|
168 // Do each in turn |
|
169 for(TInt child=0;child<children->Count();child++) |
|
170 BuildLocalL((*children)[child],ETrue); |
|
171 } |
|
172 CleanupStack::PopAndDestroy(); |
|
173 } |
|
174 |
|
175 // Called when async child completes |
|
176 void CImImap4FolderSync::DoRunL() |
|
177 { |
|
178 if (ProcessDirListL()) |
|
179 { |
|
180 // Done whole list, check to see what has vanished on the |
|
181 // server with regard to what's in the mirror: basically, |
|
182 // see what's still flagged in the tree. |
|
183 OrphanedFolderCheckL(); |
|
184 |
|
185 // All done! |
|
186 Complete(KErrNone); |
|
187 } |
|
188 else |
|
189 { |
|
190 // Otherwise, another command has been issued |
|
191 SetActive(); |
|
192 } |
|
193 } |
|
194 |
|
195 // Cancel this operation |
|
196 void CImImap4FolderSync::DoCancel() |
|
197 { |
|
198 DBG((iSession->LogText(_L8("CImImap4FolderSync::DoCancel()")))); |
|
199 |
|
200 // Cancel any outstanding session operation |
|
201 iSession->Cancel(); |
|
202 |
|
203 // ...and parent |
|
204 CMsgActive::DoCancel(); |
|
205 } |
|
206 |
|
207 // Check for orphaned folders |
|
208 void CImImap4FolderSync::OrphanedFolderCheckL() |
|
209 { |
|
210 DBG((iSession->LogText(_L8("CImImap4FolderSync::OrphanedFolderCheckL()")))); |
|
211 |
|
212 // Set stats |
|
213 iOrphanedFolders=iLocalFolders->Count(); |
|
214 |
|
215 // Selection for storing children |
|
216 CMsvEntrySelection *children=new CMsvEntrySelection; |
|
217 CleanupStack::PushL(children); |
|
218 |
|
219 // Any folders left in iLocalFolders are orphans |
|
220 for(TInt a=0;a<iLocalFolders->Count();a++) |
|
221 { |
|
222 // Go to this folder |
|
223 if (iEntry->SetEntry((*iLocalFolders)[a])!=KErrNone) |
|
224 { |
|
225 // Probably been removed already (nested?) |
|
226 continue; |
|
227 } |
|
228 TMsvEmailEntry entry=iEntry->Entry(); |
|
229 |
|
230 // Is it the INBOX? If so, ignore it |
|
231 if (entry.Parent()==iServiceId && |
|
232 entry.iDetails.CompareF(KIMAP_INBOX)==0) |
|
233 continue; |
|
234 |
|
235 // Check all the entries in the folder |
|
236 TInt subfolders=0; |
|
237 children->Reset(); |
|
238 GetChildrenL(*children); |
|
239 if (children->Count()) |
|
240 { |
|
241 // Check each message |
|
242 for(TInt a=0;a<children->Count();a++) |
|
243 { |
|
244 SetEntryL((*children)[a]); |
|
245 TMsvEmailEntry child=iEntry->Entry(); |
|
246 if (child.iType==KUidMsvMessageEntry) |
|
247 { |
|
248 // Orphan this message: it may delete it, if there |
|
249 // are no downloaded bodyparts |
|
250 |
|
251 DBG((iSession->LogText(_L8("Orphaning message %x"),(*children)[a]))); |
|
252 |
|
253 iSession->OrphanMessageL((*children)[a]); |
|
254 } |
|
255 else if (child.iType==KUidMsvFolderEntry) |
|
256 { |
|
257 // There's a subfolder |
|
258 subfolders++; |
|
259 } |
|
260 } |
|
261 } |
|
262 |
|
263 // Check number of children again: if all the orphaned children |
|
264 // have been deleted (ie, they had no downloaded body parts) |
|
265 // then we can delete the folder locally, otherwise we just have |
|
266 // to mark it as orphaned. |
|
267 SetEntryL((*iLocalFolders)[a]); |
|
268 GetChildrenL(*children); |
|
269 if ((children->Count() || subfolders>0) && !entry.Orphan()) |
|
270 { |
|
271 |
|
272 DBG((iSession->LogText(_L8("Orphaning folder %x"),(*iLocalFolders)[a]))); |
|
273 |
|
274 // Orphan folder |
|
275 TMsvEmailEntry message=iEntry->Entry(); |
|
276 message.SetOrphan(ETrue); |
|
277 ChangeEntryL(message); |
|
278 } |
|
279 else |
|
280 { |
|
281 |
|
282 DBG((iSession->LogText(_L8("Deleting folder %x"),(*iLocalFolders)[a]))); |
|
283 |
|
284 // Delete folder |
|
285 SetEntryL(iEntry->Entry().Parent()); |
|
286 iEntry->DeleteEntry((*iLocalFolders)[a]); |
|
287 } |
|
288 } |
|
289 |
|
290 // Get rid of children list |
|
291 CleanupStack::PopAndDestroy(); |
|
292 |
|
293 // Remove list |
|
294 iLocalFolders->Reset(); |
|
295 |
|
296 DBG((iSession->LogText(_L8("CImImap4FolderSync::OrphanedFolderCheckL() done")))); |
|
297 } |
|
298 |
|
299 // Process returned directory list |
|
300 TBool CImImap4FolderSync::ProcessDirListL() |
|
301 { |
|
302 // We've got a list, and the mirror folder ID that the contents belong to: |
|
303 // process it, and mirror the folder structure |
|
304 |
|
305 // First, find the existing children of this folder from the message server |
|
306 SetEntryL(iFolderId); |
|
307 GetChildrenL(*iFolderContents); |
|
308 TInt noofchildren=iFolderContents->Count(); |
|
309 |
|
310 // Number of items in reply from server |
|
311 TInt noofreplies=iFolderList->Count(); |
|
312 |
|
313 #ifdef DEBUG_FOLDERSYNC |
|
314 iSession->LogText(_L("Level %d: Server has %d entries, mirror has %d"), |
|
315 iFolderLevel,noofreplies,noofchildren); |
|
316 #endif |
|
317 |
|
318 // Empty the 'to do' array |
|
319 iFolderIds[iFolderLevel]->Reset(); |
|
320 |
|
321 // ...for each entry in reply |
|
322 for(TInt replyentry=0;replyentry<noofreplies;replyentry++) |
|
323 { |
|
324 // Does this already exist? |
|
325 TInt a; |
|
326 TMsvId thisentry; |
|
327 for(a=0;a<noofchildren;a++) |
|
328 { |
|
329 // See if the details field matches the server folder name. |
|
330 // For the INBOX folder the match should be case insensitive, all other folders |
|
331 // we must perform a case sensitive match |
|
332 SetEntryL(thisentry=(*iFolderContents)[a]); |
|
333 TPtrC entryName = iEntry->Entry().iDetails; |
|
334 TPtrC svrFolderName = (*iFolderList)[replyentry]->Leafname(); |
|
335 TBool foldersMatch = EFalse; |
|
336 if(svrFolderName.CompareF(KIMAP_INBOX)==0) |
|
337 { |
|
338 // Server folder is the INBOX |
|
339 foldersMatch = (entryName.CompareF(svrFolderName)==0); |
|
340 } |
|
341 else |
|
342 { |
|
343 // Server folder is not the INBOX |
|
344 foldersMatch = (entryName.Compare(svrFolderName)==0); |
|
345 } |
|
346 |
|
347 if (foldersMatch) |
|
348 { |
|
349 // Update mailbox flag as necessary, but NOT if it's the inbox, which is |
|
350 // *ALWAYS* a mailbox |
|
351 TMsvEmailEntry folder=iEntry->Entry(); |
|
352 if ((*iFolderList)[replyentry]->iIsMailbox!=folder.Mailbox()) |
|
353 { |
|
354 if (folder.Parent()!=iServiceId || |
|
355 folder.iDetails.CompareF(KIMAP_INBOX)!=0) |
|
356 { |
|
357 folder.SetMailbox((*iFolderList)[replyentry]->iIsMailbox); |
|
358 ChangeEntryBulkL(folder); |
|
359 } |
|
360 else |
|
361 { |
|
362 DBG((iSession->LogText(_L8("Skipping Mailbox flag twiddle for folder %x (=%d)"), |
|
363 folder.Id(),(*iFolderList)[replyentry]->iIsMailbox))); |
|
364 } |
|
365 } |
|
366 |
|
367 // Exists: remove from local folders list |
|
368 for(TInt b=0;b<iLocalFolders->Count();b++) |
|
369 { |
|
370 // Found it? |
|
371 if ((*iLocalFolders)[b]==thisentry) |
|
372 { |
|
373 // Remove from list |
|
374 iLocalFolders->Delete(b,1); |
|
375 break; |
|
376 } |
|
377 } |
|
378 |
|
379 // Exit loop |
|
380 break; |
|
381 } |
|
382 } |
|
383 |
|
384 // Found a match? |
|
385 if (a==noofchildren) |
|
386 { |
|
387 // No, create it |
|
388 TMsvEmailEntry message; |
|
389 |
|
390 // All flags unset (urrgh!) |
|
391 message.SetMtmData1(0); |
|
392 message.SetMtmData2(0); |
|
393 message.SetMtmData3(0); |
|
394 |
|
395 message.iType=KUidMsvFolderEntry; |
|
396 message.iMtm=iEntry->Entry().iMtm; |
|
397 message.iServiceId=iServiceId; |
|
398 message.iSize=0; |
|
399 message.iDetails.Set((*iFolderList)[replyentry]->Leafname()); |
|
400 message.SetValidUID(EFalse); |
|
401 message.SetComplete(ETrue); |
|
402 |
|
403 // Visibility |
|
404 if (iNewFoldersAreInvisible) |
|
405 message.SetVisible(EFalse); |
|
406 |
|
407 // Is a mailbox? (basically, is it selectable?). |
|
408 message.SetMailbox((*iFolderList)[replyentry]->iIsMailbox); |
|
409 |
|
410 #ifdef DEBUG_FOLDERSYNC |
|
411 iSession->LogText(_L("Creating folder '%S'"),&message.iDetails); |
|
412 #endif |
|
413 SetEntryL(iFolderId); |
|
414 User::LeaveIfError(iEntry->CreateEntryBulk(message)); |
|
415 |
|
416 // Increment stats |
|
417 iNewFolders++; |
|
418 |
|
419 // Get the ID, incase we're going into this level |
|
420 thisentry=message.Id(); |
|
421 } |
|
422 |
|
423 // Does this have children? (and are we at the end of our nesting levels?) |
|
424 if ((*iFolderList)[replyentry]->iIsFolder && iFolderLevel<(KFolderDepth-1)) |
|
425 { |
|
426 #ifdef DEBUG_FOLDERSYNC |
|
427 iSession->LogText(_L("Adding folder to todolist, level %d"),iFolderLevel); |
|
428 #endif |
|
429 // Add it to the list to scan at this level |
|
430 iFolderIds[iFolderLevel]->AppendL(thisentry); |
|
431 } |
|
432 } |
|
433 |
|
434 #ifdef DEBUG_FOLDERSYNC |
|
435 iSession->LogText(_L("Level %d: %d items in todolist"),iFolderLevel,iFolderIds[iFolderLevel]->Count()); |
|
436 #endif |
|
437 |
|
438 // Anything in our 'to do' queue? |
|
439 while(!iFolderIds[iFolderLevel]->Count()) |
|
440 { |
|
441 #ifdef DEBUG_FOLDERSYNC |
|
442 iSession->LogText(_L("Nothing to do at level %d"),iFolderLevel); |
|
443 #endif |
|
444 // Nothing at this level: are we at the top? |
|
445 if (iFolderLevel==0) |
|
446 { |
|
447 #ifdef DEBUG_FOLDERSYNC |
|
448 iSession->LogText(_L("At level 0: completing")); |
|
449 #endif |
|
450 // All done! |
|
451 // Commit any outstanding bulk creates |
|
452 SetEntryL(iFolderId); |
|
453 iEntry->CompleteBulk(); |
|
454 return(ETrue); |
|
455 } |
|
456 |
|
457 #ifdef DEBUG_FOLDERSYNC |
|
458 iSession->LogText(_L("Going up a level")); |
|
459 #endif |
|
460 // Not at the top: back up a level |
|
461 iFolderLevel--; |
|
462 } |
|
463 |
|
464 // Something to process: remove it from todo list |
|
465 iFolderId=(*iFolderIds[iFolderLevel])[0]; |
|
466 iFolderIds[iFolderLevel]->Delete(0,1); |
|
467 |
|
468 // Nest & issue command |
|
469 iFolderLevel++; |
|
470 iSession->ListL(iStatus,iFolderId,iFolderList); |
|
471 |
|
472 #ifdef DEBUG_FOLDERSYNC |
|
473 iSession->LogText(_L("Stuff to do at level %d - scanning dir %x"),iFolderLevel,iFolderId); |
|
474 #endif |
|
475 return(EFalse); |
|
476 } |
|
477 |
|
478 // Synchronise mirror tree with remote server |
|
479 void CImImap4FolderSync::SynchroniseTreeL(TRequestStatus& aStatus, const TMsvId aService, const TBool aNewFoldersAreInvisible) |
|
480 { |
|
481 LOG_COMMANDS((iSession->LogText(_L8("COMMAND CImImap4FolderSync::SychroniseTree (serviceid=%x)"),aService))); |
|
482 |
|
483 Queue(aStatus); |
|
484 |
|
485 // Note where this list came from |
|
486 iServiceId=aService; |
|
487 iFolderId=iServiceId; |
|
488 iFolderLevel=0; |
|
489 |
|
490 // Save invisibility state |
|
491 iNewFoldersAreInvisible=aNewFoldersAreInvisible; |
|
492 |
|
493 // Set flags on all folders, so we can tell which ones aren't matched by the |
|
494 // list returned from the server. |
|
495 iLocalFolders->Reset(); |
|
496 BuildLocalL(iServiceId,EFalse); |
|
497 |
|
498 // List this tree |
|
499 iSession->ListL(iStatus,iFolderId,iFolderList); |
|
500 SetActive(); |
|
501 } |
|
502 |
|
503 void CImImap4FolderSync::IncProgress(TImap4SyncProgress& aProgress) |
|
504 { |
|
505 aProgress.iNewFolders+=iNewFolders; |
|
506 aProgress.iOrphanedFolders+=iOrphanedFolders; |
|
507 } |
|
508 |
|
509 void CImImap4FolderSync::ResetStats() |
|
510 { |
|
511 // initialise counts |
|
512 iNewFolders=0; |
|
513 iOrphanedFolders=0; |
|
514 } |