|
1 // Copyright (c) 1997-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 // |
|
15 |
|
16 #include "agsentrymodel.h" |
|
17 |
|
18 #include "agsalarm.h" |
|
19 #include "agsasyncdelete.h" |
|
20 #include "agsattachmentindex.h" |
|
21 #include "agscategoryindex.h" |
|
22 #include "agscategorylist.h" |
|
23 #include "agsentrymanager.h" |
|
24 #include "agmentry.h" |
|
25 #include "agsextractor.h" |
|
26 #include "agsfileconverter.h" |
|
27 #include "agsiterator.h" |
|
28 #include "agsstreamidset.h" |
|
29 #include "agssort.h" |
|
30 #include "agmutil.h" |
|
31 #include "agmattendee.h" |
|
32 #include "agmcategory.h" |
|
33 #include "agssortinstance.h" |
|
34 #include "agsindex.h" |
|
35 #include "agmcontent.h" |
|
36 #include "agsfilemanager.h" |
|
37 #include "agsmain.h" |
|
38 #include "agssess.h" |
|
39 #include "agstzruleindex.h" |
|
40 #include <calnotification.h> |
|
41 #include "agsinstanceiterator.h" |
|
42 |
|
43 #include "agmdebug.h" |
|
44 |
|
45 #include <e32math.h> |
|
46 #include <e32property.h> |
|
47 #include <s32file.h> |
|
48 |
|
49 |
|
50 // Compact threshold dependent on the number of operations |
|
51 const TInt KCompactOperationsThreshold = 32; |
|
52 |
|
53 /** This is called when opening an agenda file |
|
54 @internalComponent |
|
55 */ |
|
56 CAgnEntryModel* CAgnEntryModel::NewL(CAgnServFile* aAgnServerFile) |
|
57 { |
|
58 CAgnEntryModel* self = new (ELeave) CAgnEntryModel(); |
|
59 |
|
60 CleanupStack::PushL(self); |
|
61 self->ConstructL(aAgnServerFile); |
|
62 CleanupStack::Pop(); |
|
63 |
|
64 return (self); |
|
65 } |
|
66 |
|
67 /** Constructs a CAgnEntryModel object |
|
68 @internalComponent |
|
69 */ |
|
70 void CAgnEntryModel::ConstructL(CAgnServFile* aAgnServerFile) |
|
71 |
|
72 { |
|
73 iUpdateAlarm = ETrue; |
|
74 iModelStreamIdSet = CAgnModelStreamIdSet::NewL(); |
|
75 iEntryManager = CAgnEntryManager::NewL(); |
|
76 iNextLocalUidValue = 1; |
|
77 iNextAttachmentUid = 1; |
|
78 |
|
79 if ( aAgnServerFile ) |
|
80 {//When opening a file |
|
81 iAgnServerFile = aAgnServerFile; |
|
82 iSimpleEntryTable = CAgnSimpleEntryTable::NewL(*this); |
|
83 iExtractor = new (ELeave) TAgnInstanceExtractor(*iSimpleEntryTable); |
|
84 iCategoryIndex = CAgnCategoryIndex::NewL(); |
|
85 iAttachmentIndex = new (ELeave) CAgnAttachmentIndex; |
|
86 CreateAlarmForServerL(); |
|
87 } |
|
88 iIndexFileIsDirty = ETrue; // for safety assume that the index |
|
89 // file is out of date. We can correct this |
|
90 // when we read the file |
|
91 |
|
92 iIndexFileIsPresent = ETrue; // Assume the index file is present. |
|
93 // First attempt to read it will update this |
|
94 // if it is not there. |
|
95 iTzRuleIndex = NULL; |
|
96 } |
|
97 |
|
98 /** |
|
99 Frees all resources owned by the agenda model, prior to its destruction. |
|
100 |
|
101 @internalComponent |
|
102 @capability None |
|
103 */ |
|
104 CAgnEntryModel::~CAgnEntryModel() |
|
105 { |
|
106 ResetRollback(); |
|
107 delete iCategoryIndex; |
|
108 delete iAttachmentIndex; |
|
109 delete iTzRuleIndex; |
|
110 delete iExtractor; |
|
111 delete iSimpleEntryTable; |
|
112 delete iAlarm; |
|
113 delete iModelStreamIdSet; |
|
114 delete iEntryManager; |
|
115 delete iCalConverter; |
|
116 } |
|
117 |
|
118 const CAgnServFile& CAgnEntryModel::AgnServFile() |
|
119 { |
|
120 return *iAgnServerFile; |
|
121 } |
|
122 |
|
123 /** Load up the stream network |
|
124 */ |
|
125 void CAgnEntryModel::DoOpenL(const TStreamId& aModelStreamId) |
|
126 { |
|
127 delete iCalConverter; |
|
128 iCalConverter = NULL; |
|
129 |
|
130 // Check if a calendar converter is needed (old version file) |
|
131 // |
|
132 TAgnVersion fileVersion; |
|
133 CalCommon::TCalFileVersionSupport status; |
|
134 iAgnServerFile->GetFileVersionSupportStatusL(fileVersion,status); |
|
135 |
|
136 if ( status == CalCommon::EFileNeedsConverting ) |
|
137 { |
|
138 if ( iAgnServerFile->IsReadOnly() ) |
|
139 { |
|
140 User::Leave(KErrAccessDenied); |
|
141 } |
|
142 |
|
143 iCalConverter = CalFileVersionUtils::CreateConverterL(fileVersion, *iAgnServerFile); |
|
144 iModelStreamIdSet->LoadL(StreamStore(), aModelStreamId, *iCalConverter); |
|
145 } |
|
146 else |
|
147 { |
|
148 iTzRuleIndex = CAgnTzRuleIndex::NewL(*iAgnServerFile->Dictionary(), *iAgnServerFile->StoreL()); |
|
149 iModelStreamIdSet->LoadL(StreamStore(), aModelStreamId); |
|
150 //If the Calendar file needs to be converted, CheckTzDbModificationL should be called after the file conversion is completed (in method LoadNewStreamStoreL). |
|
151 TAgnVersion curVersion = CalFileVersionUtils::CurrentFileVersion(); |
|
152 if(status == CalCommon::EFileIsCurrentVersion && !(fileVersion == curVersion)) |
|
153 { |
|
154 //access to here means the format of index file has been changed but not to the rest |
|
155 //Changing the index file format does not cause DC but we need to ignore the old index files |
|
156 iIndexFileIsDirty = EFalse; |
|
157 iIndexFileIsPresent = EFalse; |
|
158 // Mark the existing index file as dirty and delete them. |
|
159 // Trap the leave to keep things running, but there is nothing |
|
160 // we can do if the file can't be deleted. |
|
161 TRAP_IGNORE(MarkIndexFileAsDirtyL()); |
|
162 |
|
163 //Change the version of file to be current version. |
|
164 TRAP_IGNORE(iModelStreamIdSet->ChangeFileVersionL(StreamStore(), aModelStreamId, curVersion)); |
|
165 } |
|
166 } |
|
167 |
|
168 GetFileIdL(); |
|
169 InternalizeNextUidValuesL(); |
|
170 InternalizeEntryManagerL(); |
|
171 } |
|
172 |
|
173 void CAgnEntryModel::CheckTzDbModificationL() |
|
174 { |
|
175 if(iTzRuleIndex) |
|
176 { |
|
177 iTzRuleIndex->CheckTzDbModificationL(*iAgnServerFile); |
|
178 } |
|
179 } |
|
180 |
|
181 /** Opens an existing model store, whose root ID is aModelStreamId in the store aStore. |
|
182 |
|
183 @capability None |
|
184 @param aStore The store in which the model's data is stored. |
|
185 @param aId The root stream ID of the store aStore. |
|
186 |
|
187 @internalComponent |
|
188 */ |
|
189 void CAgnEntryModel::OpenL(CStreamStore& aStore, const TStreamId& aModelStreamId) |
|
190 { |
|
191 Reset(); |
|
192 |
|
193 iEntryManager->SetStore(aStore); |
|
194 DoOpenL(aModelStreamId); |
|
195 } |
|
196 |
|
197 TStreamId CAgnEntryModel::CreateL(CStreamStore& aStore) |
|
198 { |
|
199 Reset(); |
|
200 |
|
201 iEntryManager->SetStore(aStore); |
|
202 |
|
203 TStreamId headStreamId = iModelStreamIdSet->CreateL(aStore, CalFileVersionUtils::CurrentFileVersion()); |
|
204 |
|
205 // save the next unique id value to the store |
|
206 iNextLocalUidValue = 1; |
|
207 iNextAttachmentUid = 1; |
|
208 ExternalizeNextUidValuesL(); |
|
209 |
|
210 // save the entry store object to the store |
|
211 ExternalizeEntryManagerL(); |
|
212 |
|
213 TTime creationDate; |
|
214 creationDate.UniversalTime(); |
|
215 iFileId = creationDate.Int64(); |
|
216 TInt threeDidgit=Math::Random() % 1000; |
|
217 iFileId = iFileId - threeDidgit;// Ensure the file ID is unique even when two files have been created at the same time. |
|
218 ExternalizeFileIdL(aStore, iModelStreamIdSet->FileInformationStreamId()); |
|
219 StreamStore().CommitL(); |
|
220 |
|
221 return (headStreamId); |
|
222 } |
|
223 |
|
224 void CAgnEntryModel::ExternalizeFileIdL(CStreamStore& aStore, const TStreamId& aStreamId) const |
|
225 { |
|
226 RStoreWriteStream out; |
|
227 // save file id |
|
228 out.ReplaceLC(aStore, aStreamId); |
|
229 out << iFileId; |
|
230 out.CommitL(); |
|
231 CleanupStack::PopAndDestroy(); //out |
|
232 } |
|
233 |
|
234 /** |
|
235 Adds aEntry to the store and returns its resulting entry id. |
|
236 @capability None |
|
237 */ |
|
238 TAgnEntryId CAgnEntryModel::DoAddEntryL(CAgnEntry& aEntry) |
|
239 { |
|
240 // If local UID is not set or if it's already used, then the entry is assigned a new local uid. |
|
241 TBool useNextLocalUid = EFalse; |
|
242 if (aEntry.LocalUid() == 0 || iSimpleEntryTable->GetEntry(aEntry.LocalUid()) != NULL) |
|
243 { |
|
244 useNextLocalUid = ETrue; |
|
245 while (iSimpleEntryTable->GetEntry(++iNextLocalUidValue) != NULL) |
|
246 { |
|
247 } |
|
248 aEntry.SetLocalUid(iNextLocalUidValue); |
|
249 } |
|
250 |
|
251 TAgnEntryId nullId; |
|
252 aEntry.SetEntryId(nullId); |
|
253 |
|
254 StoreExternalAttributesL(aEntry); |
|
255 |
|
256 CopyAttachmentFileToDifferentPlaceL(aEntry); |
|
257 |
|
258 //Add the tz rules in the entry to tz rule index before it is stored in entry manager. |
|
259 if(iTzRuleIndex) |
|
260 { |
|
261 iTzRuleIndex->AddTzRuleL(aEntry); |
|
262 } |
|
263 |
|
264 TStreamId newStreamId = iEntryManager->AddEntryL(aEntry); |
|
265 if ( newStreamId != KNullStreamId ) |
|
266 { |
|
267 iModelStreamIdSet->EntryStreamIdSet().AddL(newStreamId); |
|
268 } |
|
269 |
|
270 TAgnEntryId entryId = aEntry.EntryId(); |
|
271 |
|
272 if ( useNextLocalUid ) |
|
273 { |
|
274 ExternalizeNextUidValuesL(); |
|
275 } |
|
276 |
|
277 iEntryManager->StoreBuffersL(); |
|
278 ExternalizeEntryManagerL(); |
|
279 |
|
280 UpdateIndexL(aEntry, NULL, EAdd); |
|
281 |
|
282 // Don't commit on add. CommitL is called from CalInterimAPI after a number have been added. |
|
283 |
|
284 return (entryId); |
|
285 } |
|
286 |
|
287 //It could copy the attachment to a different drive if it is the same as the existing one but with a different drive name |
|
288 //or, it could copy the attachment form a different calendar file folder. |
|
289 void CAgnEntryModel::CopyAttachmentFileToDifferentPlaceL(CAgnEntry& aEntry) |
|
290 { |
|
291 const TInt KAttachCount = aEntry.AttachmentCount(); |
|
292 for (TInt ii = 0; ii < KAttachCount; ++ii) |
|
293 { |
|
294 CAgnAttachment& attach = aEntry.Attachment(ii); |
|
295 if (attach.Uid() && attach.Type() == CCalContent::EDispositionInline) |
|
296 { |
|
297 CAgnAttachmentFile* attachFile = static_cast<CAgnAttachmentFile*>(&attach); |
|
298 if(IsAttachmentFileFromSameSessionL(attachFile->FileName())) |
|
299 { |
|
300 const TDesC& existFileName = iAttachmentIndex->FileName(attach.Uid()); |
|
301 if(existFileName != KNullDesC()) |
|
302 {//There exists an attachment with same uid |
|
303 TDriveName oldDrive = existFileName.Left(2); |
|
304 TDriveName newDrive = attachFile->Drive(); |
|
305 if(oldDrive.CompareF(newDrive) != 0)//drive is different |
|
306 { |
|
307 //Copy file to a new drive with the same name |
|
308 HBufC* newfileName = existFileName.AllocLC(); |
|
309 newfileName->Des().Replace(0,2,newDrive); |
|
310 iAgnServerFile->CopyFileL(existFileName, newfileName->Des()); |
|
311 attachFile->SetFileNameL(*newfileName); |
|
312 CleanupStack::PopAndDestroy(newfileName); |
|
313 } |
|
314 } |
|
315 } |
|
316 else if (iAgnServerFile->FileExistsL(attachFile->FileName())) |
|
317 { //Attachment has been copied from a different calendar session |
|
318 TParsePtrC parseOriginalFile(attachFile->FileName()); |
|
319 HBufC* fileName = GenerateFilenameLC(parseOriginalFile.Drive(), parseOriginalFile.NameAndExt()); |
|
320 iAgnServerFile->CopyFileL(attachFile->FileName(), fileName->Des()); |
|
321 attachFile->SetFileNameL(fileName->Des()); |
|
322 CleanupStack::PopAndDestroy(fileName); |
|
323 attachFile->SetUid(iNextAttachmentUid++); |
|
324 } |
|
325 } |
|
326 } |
|
327 } |
|
328 //Find out if the attachment is copied from a different server session |
|
329 TBool CAgnEntryModel::IsAttachmentFileFromSameSessionL(const TDesC& aAttachmentFile) |
|
330 { |
|
331 TBool ret = ETrue; |
|
332 if(aAttachmentFile.FindF(iAgnServerFile->PrivatePath()) != KErrNotFound) |
|
333 |
|
334 {//The file name is something, for example, |
|
335 //c:\private\private\10003a5c\calendar_filename_a\0\attachmentfilename |
|
336 //We need to find out the calendar name where the attachment belongs to see if it is the same file as the current Calendar file. |
|
337 TInt lengthPath = iAgnServerFile->PrivatePath().Length(); |
|
338 const TInt lengthDrive = 2; |
|
339 //tempRemovedPath1 = calendar_filename_a\0\attachmentfilename |
|
340 TPtrC tempRemovedPath1(aAttachmentFile.Mid(lengthPath + lengthDrive)); |
|
341 //tempRemovedPath2 = calendar_filename_a |
|
342 TPtrC tempRemovedPath2(tempRemovedPath1.Left(tempRemovedPath1.Locate('\\'))); |
|
343 //calName = calendar_filename |
|
344 TPtrC calName(tempRemovedPath2.Left(tempRemovedPath2.LocateReverse('_'))); |
|
345 TParsePtrC parseFile(iAgnServerFile->FileName()); |
|
346 if(calName.CompareF(parseFile.NameAndExt()) != 0) |
|
347 { |
|
348 ret = EFalse; |
|
349 } |
|
350 } |
|
351 return ret; |
|
352 } |
|
353 |
|
354 // aEntry is the entry that has been changed. If the change is an update, then aOriginalEntry gives |
|
355 // details about the original entry. |
|
356 void CAgnEntryModel::NotifyChangeL(const CAgnServerSession& aSession, CAgnEntry* aEntry, |
|
357 MCalChangeCallBack2::TChangeType aChangeType, CAgnInstanceInfo* aOriginalEntry) |
|
358 { |
|
359 TAgnChange change; |
|
360 change.iOperationType = aChangeType; |
|
361 |
|
362 if ( aEntry ) |
|
363 { |
|
364 change.iEntryType = aEntry->Type(); |
|
365 change.iEntryId = aEntry->LocalUid(); |
|
366 change.iRepeatRule = aEntry->RptDef(); |
|
367 change.iStartTimeOfEntryUtc = aEntry->StartTime().UtcL(); |
|
368 change.iEndTimeOfEntryUtc = aEntry->ValidToTimeLocalL(); |
|
369 |
|
370 if ( aOriginalEntry ) |
|
371 { |
|
372 change.iOriginalRepeatRule = aOriginalEntry->RptDef(); |
|
373 change.iOriginalStartTimeUtc = aOriginalEntry->StartTimeUtc(); |
|
374 change.iOriginalEndTimeUtc = aOriginalEntry->EndTimeUtc(); |
|
375 } |
|
376 else |
|
377 { |
|
378 change.iOriginalRepeatRule = NULL; |
|
379 change.iOriginalStartTimeUtc = Time::NullTTime(); |
|
380 change.iOriginalEndTimeUtc = Time::NullTTime(); |
|
381 } |
|
382 } |
|
383 else |
|
384 { |
|
385 change.iEntryType = CCalEntry::EAppt; |
|
386 change.iEntryId = 0; |
|
387 change.iStartTimeOfEntryUtc = Time::NullTTime(); |
|
388 change.iEndTimeOfEntryUtc = Time::NullTTime(); |
|
389 change.iRepeatRule = NULL; |
|
390 change.iOriginalStartTimeUtc = Time::NullTTime(); |
|
391 change.iOriginalEndTimeUtc = Time::NullTTime(); |
|
392 change.iOriginalRepeatRule = NULL; |
|
393 } |
|
394 |
|
395 change.iSession = const_cast<CAgnServerSession*>(&aSession); |
|
396 GetFileIdL(); |
|
397 change.iFileId = iFileId; |
|
398 NotifySessionsOfChangeL(change); |
|
399 } |
|
400 |
|
401 void CAgnEntryModel::NotifySessionsOfChangeL(const TAgnChange& aChange) |
|
402 { |
|
403 RPointerArray<CAgnServerSession> sessions; |
|
404 CleanupClosePushL(sessions); |
|
405 iAgnServerFile->Server().FetchSessionsL(sessions); |
|
406 const TInt count = sessions.Count(); |
|
407 for ( TInt i = 0; i < count; ++i ) |
|
408 { |
|
409 sessions[i]->AddChangeL(aChange); |
|
410 } |
|
411 CleanupStack::PopAndDestroy(&sessions); // sessions.Close() |
|
412 } |
|
413 |
|
414 void CAgnEntryModel::NotifyPublishAndSubscribeL(TAgnChangeFilter& aChangeFilter) |
|
415 { |
|
416 // if publish and subcribe is enabled... |
|
417 if ( aChangeFilter.PubSubEnabled() ) |
|
418 { |
|
419 // ...get and set the Publish and Subscribe data |
|
420 TCalPubSubData calPubSubData; |
|
421 calPubSubData.iFileNameHash = iAgnServerFile->FileNameHash(); |
|
422 calPubSubData.iTimeOfChangeUtc.UniversalTime(); |
|
423 |
|
424 TPckgBuf<TCalPubSubData> calBuf(calPubSubData); |
|
425 |
|
426 // publish the update |
|
427 if ( aChangeFilter.TodoChanged() ) |
|
428 { |
|
429 User::LeaveIfError(RProperty::Set(KCalPubSubCategory, ECalPubSubTodoNotification, calBuf)); |
|
430 } |
|
431 else |
|
432 { |
|
433 User::LeaveIfError(RProperty::Set(KCalPubSubCategory, ECalPubSubEventNotification, calBuf)); |
|
434 } |
|
435 |
|
436 aChangeFilter.SetPubSubChange(TAgnChangeFilter::ENoChange); |
|
437 } |
|
438 } |
|
439 |
|
440 /** |
|
441 Add a entry to the file. This function decides whether a new entry should be added or if an existing entry |
|
442 should be replaced. It also handles the adding of child entries. |
|
443 @internalComponent |
|
444 @return the TAgnEntryId of the newly added entry. |
|
445 @param aEntry The entry being stored. |
|
446 @param aChangeFilter Specifies the notification filter for the session adding the entry. |
|
447 @leave KErrArgument If aEntry is a parent but contains a recurrence Id. |
|
448 @leave KErrNotFound If aEntry is a child entry whose parent entry doesn't exist in the calendar store. |
|
449 @leave KErrArgument If aEntry is a child entry with a different time mode (fixed / floating) to its parent entry. |
|
450 */ |
|
451 TAgnEntryId CAgnEntryModel::StoreL(CAgnEntry& aEntry, TAgnChangeFilter* aChangeFilter) |
|
452 { |
|
453 iChangeFilter = aChangeFilter; |
|
454 TAgnEntryId returnId; |
|
455 |
|
456 if ( aEntry.GsDataType() == CGsData::EParent ) |
|
457 { |
|
458 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Entry to be stored is a parent");) |
|
459 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Checking if an entry with the same GUID already exists");) |
|
460 |
|
461 // it is a parent entry |
|
462 CAgnEntry* existingEntryToReplace = FetchEntryL(aEntry.Guid()); |
|
463 |
|
464 if (existingEntryToReplace == NULL && aEntry.LocalUid() != 0) |
|
465 { |
|
466 //This is used for the case when sync a updated entry from remote sync server. |
|
467 //Some servers may filter out GUID assigned by client and only info we can get |
|
468 //to update a entry is by using its local ID. |
|
469 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Attempting to fetch using localUid to fetch the entry from the server");) |
|
470 existingEntryToReplace = FetchEntryL(aEntry.LocalUid()); |
|
471 } |
|
472 |
|
473 if ( existingEntryToReplace) |
|
474 { |
|
475 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: An entry with the same GUID already exists");) |
|
476 |
|
477 CleanupStack::PushL(existingEntryToReplace); |
|
478 if (existingEntryToReplace->Type() == aEntry.Type()) |
|
479 { |
|
480 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Existing entry type matches incoming entry's type");) |
|
481 |
|
482 // if this entry is the same type as the existing entry, update the existing entry |
|
483 aEntry.SetEntryId(existingEntryToReplace->EntryId()); |
|
484 aEntry.SetLocalUid(existingEntryToReplace->LocalUid()); |
|
485 |
|
486 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Setting Ids using the existing entry: LocalUid= %d, EntryId= %d", aEntry.LocalUid(),aEntry.EntryId().Value());) |
|
487 |
|
488 const RArray<TGsChildRefData>& KChildIds = existingEntryToReplace->ChildIds(); |
|
489 const TInt KCount = KChildIds.Count(); |
|
490 for ( TInt ii = 0; ii < KCount; ++ii ) |
|
491 { |
|
492 aEntry.AddChildIdL(KChildIds[ii]); |
|
493 } |
|
494 |
|
495 aEntry.SetLastModifiedDate(); |
|
496 |
|
497 UpdateEntryL(aEntry, iChangeFilter, ETrue); |
|
498 |
|
499 returnId = aEntry.EntryId(); |
|
500 } |
|
501 else |
|
502 { |
|
503 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Existing entry types is different to incoming entry's type");) |
|
504 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Delete the existing entry and add the incoming entry as a new one");) |
|
505 |
|
506 // if the entry is a different type, delete the old entry and add the new one |
|
507 DeleteEntryL(*existingEntryToReplace, ETrue, iChangeFilter); |
|
508 returnId = AddEntryL(aEntry); |
|
509 } |
|
510 |
|
511 CleanupStack::PopAndDestroy(existingEntryToReplace); |
|
512 } |
|
513 else |
|
514 { |
|
515 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Matching existing entry not found. Adding a new entry");) |
|
516 returnId = AddEntryL(aEntry); |
|
517 } |
|
518 } |
|
519 else |
|
520 { |
|
521 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Entry to be stored is a child");) |
|
522 //if this is a child entry, fetch the parent from the child's GUID (if present) or Local UID |
|
523 CAgnEntry* parentEntry = NULL; |
|
524 if ( aEntry.Guid() != KNullDesC8 ) |
|
525 { |
|
526 parentEntry = FetchEntryL(aEntry.Guid()); |
|
527 } |
|
528 else |
|
529 { |
|
530 parentEntry = FetchEntryL(aEntry.ParentId()); |
|
531 } |
|
532 |
|
533 if(parentEntry==NULL) |
|
534 { |
|
535 #if defined (__CAL_BASIC_LOGGING__) || (__CAL_ENTRY_LOGGING__) || (__CAL_VERBOSE_LOGGING) |
|
536 AgmDebug::DebugLog("StoreL: KErrNotFound: Parent entry with the GUID or localUid doesnt exist"); |
|
537 #endif |
|
538 |
|
539 User::Leave(KErrNotFound); |
|
540 } |
|
541 |
|
542 CleanupStack::PushL(parentEntry); |
|
543 |
|
544 if ( parentEntry->TimeMode() != aEntry.TimeMode() || parentEntry->Type() != aEntry.Type() ) |
|
545 { |
|
546 #if defined (__CAL_BASIC_LOGGING__) || (__CAL_ENTRY_LOGGING__) || (__CAL_VERBOSE_LOGGING__) |
|
547 AgmDebug::DebugLog("StoreL: KErrArgument: Child entry with different time mode or type from the parent entry is not allowed"); |
|
548 #endif |
|
549 |
|
550 // don't allow a child entry to be different time mode or type from the parent entry |
|
551 User::Leave(KErrArgument); |
|
552 } |
|
553 |
|
554 if ( ! aEntry.RecurrenceId().IsSet()) |
|
555 { |
|
556 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Recurrence ID not set on the incoming entry. Setting it using the parent");) |
|
557 aEntry.SetRecurrenceIdFromParentL(*parentEntry); |
|
558 } |
|
559 |
|
560 // We allow only repeating parents to have children that attempt to change a parent |
|
561 // schedule.(If the user's intention is to modify a non-repeating parent, the entire |
|
562 // parent should be replaced with a new entry). |
|
563 const CAgnRptDef* KParentRptDef = parentEntry->RptDef(); |
|
564 |
|
565 if(KParentRptDef==NULL) |
|
566 { |
|
567 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Parent is not repeating. Only repeating parents are allowed to have children");) |
|
568 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: If the user's intention is to modify a non-repeating parent, the entire parent should be replaced with a new entry");) |
|
569 |
|
570 User::Leave(KErrArgument); |
|
571 } |
|
572 |
|
573 if ( aEntry.RptDef() ) |
|
574 { |
|
575 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Incoming entry is a repeating child entry");) |
|
576 returnId = AddRepeatingChildEntryUpdateParentRuleL(*parentEntry, aEntry); |
|
577 } |
|
578 else |
|
579 { |
|
580 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Incoming entry is a non-repeating child entry");) |
|
581 returnId = AddNonRepeatingChildEntryUpdateParentExceptionsL(*parentEntry, aEntry); |
|
582 } |
|
583 |
|
584 CleanupStack::PopAndDestroy(parentEntry); |
|
585 } |
|
586 _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Entry added successfully");) |
|
587 _DBGLOG_ENTRY(AgmDebug::DebugLogEntryL(aEntry, EDumpEntryAll);) |
|
588 |
|
589 return (returnId); |
|
590 } |
|
591 |
|
592 TStreamId CAgnEntryModel::WriteDescriptorToStreamL(const TDesC& aString) |
|
593 { |
|
594 if (aString.Length() > 0) |
|
595 { |
|
596 RStoreWriteStream out; |
|
597 TStreamId id = out.CreateLC(StreamStore()); |
|
598 out.WriteUint32L(aString.Length()); |
|
599 out << aString; |
|
600 out.CommitL(); |
|
601 CleanupStack::PopAndDestroy(); //out |
|
602 return id; |
|
603 } |
|
604 return KNullStreamId; |
|
605 } |
|
606 |
|
607 /** |
|
608 Store those entry properties which are stored in a separate stream: summary, description, alarm action. |
|
609 @capability WriteUserData |
|
610 */ |
|
611 void CAgnEntryModel::StoreExternalAttributesL(CAgnEntry& aEntry) |
|
612 { |
|
613 TStreamId id = WriteDescriptorToStreamL(aEntry.Description()); |
|
614 aEntry.SetDescriptionStreamId(id); |
|
615 |
|
616 id = WriteDescriptorToStreamL(aEntry.Summary()); |
|
617 aEntry.SetSummaryStreamId(id); |
|
618 |
|
619 if ( aEntry.AlarmAction() ) |
|
620 { |
|
621 RStoreWriteStream out; |
|
622 id = out.CreateLC(StreamStore()); |
|
623 out << *aEntry.AlarmAction(); |
|
624 out.CommitL(); |
|
625 CleanupStack::PopAndDestroy(); //out |
|
626 aEntry.SetAlarmActionStreamId(id); |
|
627 } |
|
628 } |
|
629 |
|
630 void CAgnEntryModel::DeleteExternalAttributesL(CAgnEntry& aEntry) |
|
631 { |
|
632 if ( aEntry.DescriptionStreamId() != KNullStreamId ) |
|
633 { |
|
634 StreamStore().DeleteL(aEntry.DescriptionStreamId()); |
|
635 aEntry.SetDescriptionStreamId(KNullStreamId); |
|
636 } |
|
637 |
|
638 if ( aEntry.SummaryStreamId() != KNullStreamId ) |
|
639 { |
|
640 StreamStore().DeleteL(aEntry.SummaryStreamId()); |
|
641 aEntry.SetSummaryStreamId(KNullStreamId); |
|
642 } |
|
643 |
|
644 if ( aEntry.AlarmActionStreamId() != KNullStreamId ) |
|
645 { |
|
646 StreamStore().DeleteL(aEntry.AlarmActionStreamId()); |
|
647 aEntry.SetAlarmActionStreamId(KNullStreamId); |
|
648 } |
|
649 } |
|
650 |
|
651 void CAgnEntryModel::UpdateExternalAttributesL(CAgnEntry& aEntry) |
|
652 { |
|
653 // 3 scenarios where we do something for updating notes text |
|
654 TStreamId id = KNullStreamId; |
|
655 |
|
656 switch ( aEntry.DescriptionChange() ) |
|
657 { |
|
658 case CAgnEntry::EAgnDataDeleted: |
|
659 DeleteTextStreamL(aEntry.DescriptionStreamId()); |
|
660 aEntry.SetDescriptionStreamId(KNullStreamId); |
|
661 break; |
|
662 |
|
663 case CAgnEntry::EAgnDataAdded: |
|
664 id = StoreTextL(aEntry.Description()); |
|
665 aEntry.SetDescriptionStreamId(id); |
|
666 break; |
|
667 |
|
668 case CAgnEntry::EAgnDataUpdated: |
|
669 UpdateTextL(aEntry.Description(), aEntry.DescriptionStreamId()); |
|
670 break; |
|
671 |
|
672 default://no changes |
|
673 break; |
|
674 } |
|
675 |
|
676 // 3 scenarios where we do something for updating summary text |
|
677 switch ( aEntry.SummaryChange() ) |
|
678 { |
|
679 case CAgnEntry::EAgnDataDeleted: |
|
680 DeleteTextStreamL(aEntry.SummaryStreamId()); |
|
681 aEntry.SetSummaryStreamId(KNullStreamId); |
|
682 break; |
|
683 |
|
684 case CAgnEntry::EAgnDataAdded: |
|
685 id = StoreTextL(aEntry.Summary()); |
|
686 aEntry.SetSummaryStreamId(id); |
|
687 break; |
|
688 |
|
689 case CAgnEntry::EAgnDataUpdated: |
|
690 UpdateTextL(aEntry.Summary(), aEntry.SummaryStreamId()); |
|
691 break; |
|
692 |
|
693 default://no changes |
|
694 break; |
|
695 } |
|
696 |
|
697 // 3 scenarios where we do something for updating alarm action |
|
698 switch ( aEntry.AlarmActionChange() ) |
|
699 { |
|
700 case CAgnEntry::EAgnDataDeleted: |
|
701 DeleteAlarmActionStreamL(aEntry.AlarmActionStreamId()); |
|
702 aEntry.SetAlarmActionStreamId(KNullStreamId); |
|
703 break; |
|
704 |
|
705 case CAgnEntry::EAgnDataAdded: |
|
706 id = StoreAlarmActionL(*(aEntry.AlarmAction())); |
|
707 aEntry.SetAlarmActionStreamId(id); |
|
708 break; |
|
709 |
|
710 case CAgnEntry::EAgnDataUpdated: |
|
711 UpdateAlarmActionL(*(aEntry.AlarmAction()), aEntry.AlarmActionStreamId()); |
|
712 break; |
|
713 |
|
714 default://no changes |
|
715 break; |
|
716 } |
|
717 } |
|
718 |
|
719 |
|
720 TAgnEntryId CAgnEntryModel::AddEntryL(CAgnEntry& aEntry) |
|
721 { |
|
722 TAgnEntryId id; |
|
723 |
|
724 id = DoAddEntryL(aEntry); |
|
725 |
|
726 NotifyingL(MCalChangeCallBack2::EChangeAdd, aEntry, NULL); |
|
727 |
|
728 if (iUpdateAlarm && aEntry.HasAlarm()) |
|
729 { |
|
730 iAlarm->FindAndQueueNextAlarmL(EFalse); |
|
731 } |
|
732 |
|
733 return (id); |
|
734 } |
|
735 |
|
736 |
|
737 // Update all the indexes which are in RAM after an entry operation (add, update, delete) |
|
738 void CAgnEntryModel::UpdateIndexL(CAgnEntry& aEntry, CAgnEntry* aOldEntry, TUpdateIndex aUpdateIndex) |
|
739 { |
|
740 // set guid hash before adding / updating an entry in the GUID hash index |
|
741 if ( aEntry.GsDataType() == CGsData::EParent && ! aEntry.GuidHash() ) |
|
742 { |
|
743 aEntry.SetGuidHash( GenerateHash8L(aEntry.Guid()) ); |
|
744 if ( aOldEntry ) |
|
745 { |
|
746 aOldEntry->SetGuidHash( aEntry.GuidHash() ); |
|
747 } |
|
748 } |
|
749 |
|
750 switch( aUpdateIndex ) |
|
751 { |
|
752 case EAdd: |
|
753 { |
|
754 AppendRollbackArrayL(aEntry, ETrue); |
|
755 |
|
756 AddEntryToIndexesL(aEntry); |
|
757 } |
|
758 break; |
|
759 |
|
760 case EDelete: |
|
761 { |
|
762 AppendRollbackArrayL(aEntry, EFalse); |
|
763 |
|
764 iSimpleEntryTable->DeleteEntry(aEntry.EntryId()); |
|
765 |
|
766 iCategoryIndex->DeleteEntryL(aEntry.EntryId()); |
|
767 iAttachmentIndex->DeleteEntryL(aEntry); |
|
768 } |
|
769 break; |
|
770 |
|
771 case EUpdate: |
|
772 { |
|
773 __ASSERT_ALWAYS(aOldEntry, Panic(EAgmErrUpdateInvalid)); |
|
774 AppendRollbackArrayL(*aOldEntry, EFalse); |
|
775 iSimpleEntryTable->DeleteEntry(aOldEntry->EntryId()); |
|
776 |
|
777 AppendRollbackArrayL(aEntry, ETrue); |
|
778 |
|
779 AddEntryToIndexesL(aEntry); |
|
780 } |
|
781 break; |
|
782 |
|
783 case EBuildIndex: |
|
784 { |
|
785 AppendRollbackArrayL(aEntry, ETrue); |
|
786 |
|
787 if(iTzRuleIndex) |
|
788 { |
|
789 iTzRuleIndex->FetchTzRuleL(aEntry); |
|
790 } |
|
791 AddEntryToIndexesL(aEntry); |
|
792 } |
|
793 break; |
|
794 |
|
795 default: |
|
796 Panic(EAgmErrInvalidIndexUpdate); |
|
797 break; |
|
798 } |
|
799 // Increment the operations counter which triggers compacting upon reaching a threshold |
|
800 ++iOperationsCounter; |
|
801 } |
|
802 |
|
803 // Add an entry to all indexes in RAM |
|
804 void CAgnEntryModel::AddEntryToIndexesL(CAgnEntry& aEntry) |
|
805 { |
|
806 aEntry.SetCollectionId(iAgnServerFile->CollectionId()); |
|
807 iSimpleEntryTable->AddEntryL(aEntry); |
|
808 |
|
809 iAgnServerFile->CategoryList().AddEntryL(aEntry); |
|
810 iCategoryIndex->UpdateEntryL(aEntry.EntryId(), aEntry); |
|
811 iAttachmentIndex->AddEntryL(aEntry); |
|
812 } |
|
813 |
|
814 /** |
|
815 Returns true if the entry is found to have any children which are repeating. |
|
816 @internalComponent |
|
817 @return TBool. |
|
818 @param aEntry The entry. |
|
819 */ |
|
820 TBool CAgnEntryModel::EntryHasRepeatingChildrenL(const CAgnEntry& aParentEntry) |
|
821 { |
|
822 const RArray<TGsChildRefData>& KChildIds = aParentEntry.ChildIds(); |
|
823 |
|
824 const TInt KIdCount = KChildIds.Count(); |
|
825 |
|
826 TBool repeatingChildExists = EFalse; |
|
827 |
|
828 for ( TInt pos = 0; ! repeatingChildExists && pos < KIdCount; ++pos ) |
|
829 { |
|
830 CAgnEntry* childOfParentEntry = FetchEntryL(KChildIds[pos].ChildId()); |
|
831 if (childOfParentEntry) |
|
832 { |
|
833 repeatingChildExists = (childOfParentEntry && childOfParentEntry->RptDef()); |
|
834 |
|
835 delete childOfParentEntry; |
|
836 } |
|
837 } |
|
838 |
|
839 return (repeatingChildExists); |
|
840 } |
|
841 |
|
842 /** |
|
843 Deletes any non-repeating children within a specified range. This is called when adding a repeating child entry to |
|
844 delete existing non-repeating child entries - those that appear either before or after the repeating child entry's recurrence ID, |
|
845 depending on the recurrence range. |
|
846 @internalComponent |
|
847 @param aParentEntry The parent entry |
|
848 @param aRecId Indicates the recurrence ID of the repeating child entry |
|
849 @param aRange Indicates "this and prior" or "this and future" to delete child entries on one side of the recurrence ID. |
|
850 */ |
|
851 void CAgnEntryModel::DeleteNonRepeatingChildrenOutsideRangeL(const CAgnEntry& aParentEntry, const TAgnCalendarTime& aRecId, CalCommon::TRecurrenceRange aRange) |
|
852 { |
|
853 const RArray<TGsChildRefData>& KChildIds = aParentEntry.ChildIds(); |
|
854 |
|
855 const TInt KCount(KChildIds.Count()); |
|
856 |
|
857 for ( TInt i = 0; i < KCount; ++i ) |
|
858 { |
|
859 // Check if any child falls outside parent's rpt rule |
|
860 if ( (aRecId > KChildIds[i].RecurrenceId() && aRange == CalCommon::EThisAndPrior) || |
|
861 (aRecId < KChildIds[i].RecurrenceId() && aRange == CalCommon::EThisAndFuture) ) |
|
862 { |
|
863 // Remove child if it is non-repeating |
|
864 TCalLocalUid chIdRemove(KChildIds[i].ChildId()); |
|
865 CAgnEntry* chIdEntry = FetchEntryL(chIdRemove); |
|
866 if (chIdEntry) |
|
867 { |
|
868 CleanupStack::PushL(chIdEntry); |
|
869 |
|
870 __ASSERT_ALWAYS( ! chIdEntry->RptDef(), Panic(EAgmErrAddingSecondRepeatingChildEntry)); |
|
871 DeleteEntryL(*chIdEntry, ETrue, iChangeFilter); |
|
872 |
|
873 CleanupStack::PopAndDestroy(chIdEntry); |
|
874 } |
|
875 } |
|
876 } |
|
877 } |
|
878 |
|
879 /** |
|
880 Adds a repeating child to a repeating parent, and performs the necessary adjustments to the parent's repeat rule. |
|
881 The explanation of how the parent's rpt rule is trimmed is given in the code comments. |
|
882 @internalComponent |
|
883 @param aParentEntry The parent entry |
|
884 @param aRepeatingChild The child to be added |
|
885 @leave KErrArgument If either aParentEntry or aRepeatingChild are non-repeating |
|
886 @leave KErrArgument If aRepeatingChild's RecurrenceId is aParentEntry's first or last instance |
|
887 @leave KErrNotSupported If aRepeatingChild's Range is ECurrentInstance |
|
888 @leave KErrArgument If aRepeatingChild's Range is an unexpected value |
|
889 */ |
|
890 TAgnEntryId CAgnEntryModel::AddRepeatingChildEntryUpdateParentRuleL(CAgnEntry& aParentEntry, CAgnEntry& aRepeatingChild) |
|
891 { |
|
892 // Ensure both parent and child are repeating |
|
893 __ASSERT_ALWAYS((aRepeatingChild.RptDef() && aParentEntry.RptDef()), User::Leave(KErrArgument)); |
|
894 TAgnCalendarTime childRecId = aRepeatingChild.RecurrenceId(); |
|
895 |
|
896 TAgnEntryId retId; |
|
897 |
|
898 CAgnRptDef* parentRptDef = aParentEntry.RptDef(); |
|
899 |
|
900 TTime recIdLocal(childRecId.LocalL()); |
|
901 TDateTime recIdLocalDateTime(recIdLocal.DateTime()); |
|
902 |
|
903 CalCommon::TRecurrenceRange range = aRepeatingChild.RecurrenceRange(); |
|
904 |
|
905 if(range == CalCommon::EThisOnly) |
|
906 { |
|
907 // if the range specified is EThisOnly, attempt to recalculate the recurrence range |
|
908 // so that the maximum number of "overlapping" entries (schedules running concurrently in the |
|
909 // parent and child) is removed |
|
910 TTime dtStartLocal = aParentEntry.StartTime().LocalL(); |
|
911 TDateTime dtStartLocalDateTime =(dtStartLocal.DateTime()); |
|
912 if (dtStartLocalDateTime.Year() == recIdLocalDateTime.Year() && |
|
913 dtStartLocalDateTime.Month() == recIdLocalDateTime.Month() && |
|
914 dtStartLocalDateTime.Day() == recIdLocalDateTime.Day()) |
|
915 { |
|
916 // Check whether the recurrence ID of the child is of the same day as the |
|
917 // first instance of the parent. If so, assume range is ThisAndPrior |
|
918 // to avoid the parent repeating rule being completely trimmed. |
|
919 // |
|
920 // Note: It is possible that this is a previously trimmed parent, as a result of |
|
921 // the trimming, completely embedded by the child. Therefore, no rejection of |
|
922 // such child/parent range is performed to ensure an Import->Export->Import will |
|
923 // not be failed. In the rare occurrence where the child entry completely embeds |
|
924 // the parent entry and has RecID on the same day as start/end of parent entry, it will |
|
925 // be allowed through. |
|
926 range = CalCommon::EThisAndPrior; |
|
927 } |
|
928 else |
|
929 { |
|
930 TTime dtUntilLocal = parentRptDef->LastInstanceL().LocalL(); |
|
931 |
|
932 TDateTime dtUntilLocalDateTime =(dtUntilLocal.DateTime()); |
|
933 if (dtUntilLocalDateTime.Year() == recIdLocalDateTime.Year() && |
|
934 dtUntilLocalDateTime.Month() == recIdLocalDateTime.Month() && |
|
935 dtUntilLocalDateTime.Day() == recIdLocalDateTime.Day()) |
|
936 { |
|
937 // Check whether the recurrence ID of the child is of the same day as the |
|
938 // last instance of the parent. If so, assume range is ThisAndFuture |
|
939 // instances to avoid the parent repeating rule being completely trimmed. |
|
940 // |
|
941 // Note: It is possible that this is a previously trimmed parent, as a result of |
|
942 // the trimming, completely embedded by the child. Therefore, no rejection of |
|
943 // such child/parent range is performed to ensure an Import->Export->Import will |
|
944 // not be failed. In the rare occurrence where the child entry completely |
|
945 // embeds the parent entry and has RecID on the same day as start/end of parent |
|
946 // entry, it will be allowed through. |
|
947 range = CalCommon::EThisAndFuture; |
|
948 } |
|
949 else |
|
950 { |
|
951 // Reject the child entry if it completely alters the parent entry. This should be done by |
|
952 // replacing the parent schedule instead. |
|
953 // |
|
954 // An exception is when the parent entry's start/end date is the same as the RecID. That |
|
955 // could be the result of the parent being previously trimmed. |
|
956 __ASSERT_ALWAYS(parentRptDef->LastInstanceL() > aRepeatingChild.RptDef()->LastInstanceL() || |
|
957 aRepeatingChild.RptDef()->FirstInstanceL() > aParentEntry.StartTime(), |
|
958 User::Leave(KErrArgument)); |
|
959 // if the RecId is neither at the start/end of the parent's rpt range, |
|
960 // then base on the distance of the beginning & end of the child recurreance range |
|
961 // from the RecId, determine if the range should be assumed as ThisAndFuture |
|
962 // or a ThisAndPrior scenario. |
|
963 // |
|
964 // As this usage is most likely ThisAndFuture, so even if the distance from |
|
965 // beginning & end of the child repeat range is the same, assume CurrentAndFuture. |
|
966 TTimeIntervalDays afterRecurrence = (aRepeatingChild.RptDef()->LastInstanceL().UtcL()).DaysFrom(childRecId.UtcL()); |
|
967 TTimeIntervalDays beforeRecurrence = childRecId.UtcL().DaysFrom(aRepeatingChild.RptDef()->FirstInstanceL().UtcL()); |
|
968 range = (afterRecurrence >= beforeRecurrence) ? |
|
969 CalCommon::EThisAndFuture : CalCommon::EThisAndPrior; |
|
970 } |
|
971 } |
|
972 aRepeatingChild.SetRecurrenceRangeL(range); |
|
973 } |
|
974 |
|
975 // Trim parent's repeat rule to fall in line with the new child entry, |
|
976 // and deal with any exceptions and sporadic dates that fall within the |
|
977 // affected period. |
|
978 |
|
979 switch (range) |
|
980 { |
|
981 case CalCommon::EThisAndFuture: |
|
982 { |
|
983 // Reject if RecurrenceId falls on 1st instance of parent's (original) rpt rule |
|
984 // If the User's intention is to modify the entire parent schedule, they should replace the |
|
985 // existing parent with a new parent entry (by submitting an entry with no RecId specified). |
|
986 TTime firstInstanceParentUtc(Time::NullTTime()); |
|
987 parentRptDef->NudgeNextInstanceUtcL(aParentEntry.StartTime().UtcL(), firstInstanceParentUtc); |
|
988 |
|
989 // Ensure child's RecurrenceId is not the parent's first instance. |
|
990 __ASSERT_ALWAYS(childRecId.UtcL() > firstInstanceParentUtc, User::Leave(KErrArgument)); |
|
991 |
|
992 // Store child entry |
|
993 retId = AddChildEntryL(aRepeatingChild, aParentEntry); |
|
994 childRecId = aRepeatingChild.RecurrenceId(); // recurrence ID may have been updated if it was an imported rec ID with no time |
|
995 |
|
996 // Set parent's rpt-rule end date to child's RecId (already verified RecId points to a genuine instance on the parent). |
|
997 if (parentRptDef->RRule()) |
|
998 { |
|
999 parentRptDef->SetUntilTime(childRecId); |
|
1000 } |
|
1001 } |
|
1002 break; |
|
1003 |
|
1004 case CalCommon::EThisAndPrior: |
|
1005 { |
|
1006 // Reject if RecurrenceId falls on last instance of parent's (original) rpt rule |
|
1007 // If the User's intention is to modify the entire parent schedule, they should replace the |
|
1008 // existing parent with a new parent entry (by submitting an entry with no RecId specified). |
|
1009 TTime lastInstanceParentUtc(Time::NullTTime()); |
|
1010 parentRptDef->NudgePreviousInstanceUtcL(parentRptDef->LastInstanceL().UtcL(), lastInstanceParentUtc); |
|
1011 |
|
1012 // Ensure child's RecurrenceId is not the parent's last instance. |
|
1013 __ASSERT_ALWAYS(childRecId.UtcL() < lastInstanceParentUtc, User::Leave(KErrArgument)); |
|
1014 |
|
1015 // Store child entry |
|
1016 retId = AddChildEntryL(aRepeatingChild, aParentEntry); |
|
1017 childRecId = aRepeatingChild.RecurrenceId(); // recurrence ID may have been updated if it was an imported rec ID with no time |
|
1018 |
|
1019 // Move parent's start time to child's RecId (already verified RecId points to a genuine instance on the parent). |
|
1020 aParentEntry.MoveStartTimeLocalL(childRecId.LocalL()); |
|
1021 } |
|
1022 break; |
|
1023 |
|
1024 default: |
|
1025 { |
|
1026 User::Leave(KErrArgument); |
|
1027 } |
|
1028 break; |
|
1029 } |
|
1030 |
|
1031 // add an exception to the parent on the child's recurrence ID |
|
1032 aParentEntry.RptDef()->AddExceptionL(childRecId); |
|
1033 |
|
1034 // Remove any exceptions on the parent that fall in the discarded range |
|
1035 aParentEntry.RptDef()->PruneExceptionsL(); |
|
1036 |
|
1037 // Remove any sporadic dates on the parent that fall in the discarded range |
|
1038 aParentEntry.PruneRDatesL(childRecId, range); |
|
1039 |
|
1040 // Commit parent to store |
|
1041 aParentEntry.SetLastModifiedDate(); |
|
1042 UpdateEntryL(aParentEntry, iChangeFilter, EFalse); |
|
1043 |
|
1044 // Delete any non-repeating children that fall in the discarded range |
|
1045 DeleteNonRepeatingChildrenOutsideRangeL(aParentEntry, childRecId, range); |
|
1046 |
|
1047 return (retId); |
|
1048 } |
|
1049 |
|
1050 |
|
1051 /** |
|
1052 Adds a non-repeating child to a repeating parent, and then adds an exception to the parent's exception |
|
1053 list for the occurrence given by the child's RecId. |
|
1054 @internalComponent |
|
1055 @param aParentEntry The parent entry |
|
1056 @param aNonRepeatingChild The child to be added |
|
1057 @leave KErrArgument If parent entry does not have a repeat definition |
|
1058 */ |
|
1059 TAgnEntryId CAgnEntryModel::AddNonRepeatingChildEntryUpdateParentExceptionsL(CAgnEntry& aParentEntry, CAgnEntry& aNonRepeatingChild) |
|
1060 { |
|
1061 const TInt KNumchildrenBefore = aParentEntry.ChildIds().Count(); |
|
1062 // Add\Update children entry |
|
1063 TAgnEntryId retId = AddChildEntryL(aNonRepeatingChild, aParentEntry); |
|
1064 const TInt KNumchildrenAfter = aParentEntry.ChildIds().Count(); |
|
1065 |
|
1066 TBool addexception = !aParentEntry.RptDef()->FindException(aNonRepeatingChild.RecurrenceId()); |
|
1067 if ( KNumchildrenAfter > KNumchildrenBefore || addexception ) |
|
1068 { |
|
1069 //only add exception and update parent if a child has been added otherwise a existing child has been updated. |
|
1070 aParentEntry.RptDef()->AddExceptionL(aNonRepeatingChild.RecurrenceId()); |
|
1071 aParentEntry.SetLastModifiedDate(); |
|
1072 UpdateEntryL(aParentEntry, iChangeFilter, EFalse); |
|
1073 } |
|
1074 |
|
1075 return (retId); |
|
1076 } |
|
1077 |
|
1078 |
|
1079 TAgnEntryId CAgnEntryModel::AddChildEntryL(CAgnEntry& aChild, CAgnEntry& aParent) |
|
1080 { |
|
1081 TAgnCalendarTime entryRecId = aChild.RecurrenceId(); |
|
1082 CAgnRptDef* parentRptDef = aParent.RptDef(); |
|
1083 __ASSERT_ALWAYS(parentRptDef, Panic(EAgmErrAddingChildEntryToNonRepeatingParent)); |
|
1084 |
|
1085 // Microsoft export recurrence id with date but no time. Test if current |
|
1086 // recurrenceId is an instance of parent entry so that when the time is |
|
1087 // not specified in recurrenceId we can find the right occurence from the parent. |
|
1088 if ( ! parentRptDef->IsAnInstanceL(entryRecId.LocalL()) ) |
|
1089 { |
|
1090 // If a midnight recurrence Id is not floating, and is not an instance of the parent entry, then |
|
1091 // it means it is imported without the time and timezone information. Therefore, it has to be |
|
1092 // converted to a correct UTC time here before nudging the instance. |
|
1093 TDateTime recIdDateTime = entryRecId.UtcL().DateTime(); |
|
1094 if (recIdDateTime.Hour() == 0 && recIdDateTime.Minute() == 0 && |
|
1095 aChild.TimeMode() != MAgnCalendarTimeMode::EFloating) |
|
1096 { |
|
1097 entryRecId.SetUtcL(parentRptDef->ConvertFromRepeatLocalToUtcL(entryRecId.UtcL())); |
|
1098 } |
|
1099 |
|
1100 TTime actualInstanceTimeUtc; |
|
1101 parentRptDef->NudgeNextInstanceUtcL(entryRecId.UtcL(), actualInstanceTimeUtc); |
|
1102 // If recurrenceId is not an instance of the parent then nudge to next occurence if an instance can be found for the same date. |
|
1103 if ( actualInstanceTimeUtc != Time::NullTTime() ) |
|
1104 { |
|
1105 const TTime KActualInstanceTimeRptLocal = parentRptDef->ConvertFromUtcToRepeatLocalL(actualInstanceTimeUtc); |
|
1106 const TTime KRecurrenceIdRptLocal = parentRptDef->ConvertFromUtcToRepeatLocalL(entryRecId.UtcL()); |
|
1107 |
|
1108 const TDateTime KActualInstanceDateTimeRptLocal = KActualInstanceTimeRptLocal.DateTime(); |
|
1109 const TDateTime KRecurrenceIdDateTimeRptLocal = KRecurrenceIdRptLocal.DateTime(); |
|
1110 |
|
1111 // Only change recurrenceId if nudged date is the same as the recurrenceId date |
|
1112 if ( KRecurrenceIdDateTimeRptLocal.Year() == KActualInstanceDateTimeRptLocal.Year() && |
|
1113 KRecurrenceIdDateTimeRptLocal.Month() == KActualInstanceDateTimeRptLocal.Month() && |
|
1114 KRecurrenceIdDateTimeRptLocal.Day() == KActualInstanceDateTimeRptLocal.Day() ) |
|
1115 { |
|
1116 if (aParent.TimeMode() == MAgnCalendarTimeMode::EFloating) |
|
1117 { |
|
1118 entryRecId.SetFloatingL(KActualInstanceTimeRptLocal); |
|
1119 } |
|
1120 else |
|
1121 { |
|
1122 entryRecId.SetUtcL(actualInstanceTimeUtc); |
|
1123 } |
|
1124 aChild.UpdateRecurrenceIdL(entryRecId); |
|
1125 } |
|
1126 else |
|
1127 { |
|
1128 User::Leave(KErrNotFound); // If leave occurs here then there is no event found for given day by recurrenceId |
|
1129 } |
|
1130 } |
|
1131 else |
|
1132 { |
|
1133 User::Leave(KErrNotFound); // leave occurs here because the next instance cannot be found |
|
1134 } |
|
1135 } |
|
1136 |
|
1137 aChild.SetParentId(aParent.LocalUid()); |
|
1138 // First check if the same child entry already exists in the store (one with the same RecId). |
|
1139 // If so we delete it and replace it with the new child. |
|
1140 // This allows the client to 'update' a child entry without having to destroy the entire |
|
1141 // associated set. |
|
1142 |
|
1143 CAgnEntry* existingChildEntry = FetchEntryL(aParent.Guid(), entryRecId); |
|
1144 |
|
1145 TAgnEntryId retId; |
|
1146 TBool addChild(ETrue); |
|
1147 |
|
1148 if ( existingChildEntry ) |
|
1149 { |
|
1150 CleanupStack::PushL(existingChildEntry); |
|
1151 if ( existingChildEntry->Type() == aChild.Type() ) |
|
1152 {// Update existing child entry |
|
1153 aChild.SetLocalUid(existingChildEntry->LocalUid()); |
|
1154 aChild.SetEntryId(existingChildEntry->EntryId()); |
|
1155 UpdateEntryL(aChild, iChangeFilter, EFalse); |
|
1156 retId = aChild.EntryId(); |
|
1157 addChild = EFalse; |
|
1158 } |
|
1159 else |
|
1160 { |
|
1161 // Delete existing child entry |
|
1162 aChild.SetLocalUid(existingChildEntry->LocalUid()); |
|
1163 DeleteEntryL(*existingChildEntry, EFalse, iChangeFilter); |
|
1164 } |
|
1165 CleanupStack::PopAndDestroy(existingChildEntry); |
|
1166 } |
|
1167 |
|
1168 if ( addChild ) |
|
1169 { |
|
1170 if ( aChild.RptDef() ) |
|
1171 { |
|
1172 // Check there are no other repeating childs on this parent. |
|
1173 // (Limit to only ONE rpt rule change to a parent entry) |
|
1174 __ASSERT_ALWAYS(!EntryHasRepeatingChildrenL(aParent), User::Leave(KErrNotSupported)); |
|
1175 } |
|
1176 |
|
1177 // add the new child entry |
|
1178 retId = AddEntryL(aChild); |
|
1179 |
|
1180 // Add the occurrence given by the RecId to the parent's Exception List. |
|
1181 TGsChildRefData child(aChild.LocalUid(), entryRecId); |
|
1182 aParent.AddChildIdL(child); |
|
1183 } |
|
1184 |
|
1185 return (retId); |
|
1186 } |
|
1187 |
|
1188 |
|
1189 CAgnEntry* CAgnEntryModel::FetchEntryL(const TDesC8& aGuid) const |
|
1190 { |
|
1191 #ifdef __CAL_VERBOSE_LOGGING__ |
|
1192 { |
|
1193 TBuf<KMaxGuidBufLength> guidBuf; |
|
1194 guidBuf.Copy(aGuid); |
|
1195 AgmDebug::DebugLog("FetchEntryL: Fetching entry with GUID='%S'",&guidBuf); |
|
1196 } |
|
1197 #endif |
|
1198 |
|
1199 RArray<TAgnEntryId> candidateMatches; |
|
1200 CleanupClosePushL(candidateMatches); |
|
1201 |
|
1202 iSimpleEntryTable->FindByHashL(GenerateHash8L(aGuid), candidateMatches); |
|
1203 |
|
1204 CAgnEntry* entry = NULL; |
|
1205 const TInt KCount = candidateMatches.Count(); |
|
1206 |
|
1207 for ( TInt i = 0; i < KCount; ++i ) |
|
1208 { |
|
1209 CAgnEntry* candidateEntry = FetchEntryL(candidateMatches[i]); |
|
1210 __ASSERT_ALWAYS(candidateEntry, User::Leave(KErrNotFound)); // entry table contains an entry not in the store |
|
1211 |
|
1212 CleanupStack::PushL(candidateEntry); |
|
1213 |
|
1214 if ( candidateEntry->Guid() == aGuid ) |
|
1215 { |
|
1216 entry = candidateEntry; |
|
1217 CleanupStack::Pop(candidateEntry); |
|
1218 break; |
|
1219 } |
|
1220 |
|
1221 CleanupStack::PopAndDestroy(candidateEntry); |
|
1222 } |
|
1223 |
|
1224 CleanupStack::PopAndDestroy(); //candidateMatches.Close(); |
|
1225 |
|
1226 return (entry); |
|
1227 } |
|
1228 |
|
1229 |
|
1230 CAgnEntry* CAgnEntryModel::FindChildFromParentL(const CAgnEntry& aParent, const TAgnCalendarTime& aRecurrenceId) const |
|
1231 { |
|
1232 CAgnEntry* returnEntry = NULL; |
|
1233 |
|
1234 // Get Child ids |
|
1235 const RArray<TGsChildRefData>& KChildIds = aParent.ChildIds(); |
|
1236 |
|
1237 // Check if we have a match amongst the children of this parent |
|
1238 const TInt KCount = KChildIds.Count(); |
|
1239 |
|
1240 for ( TInt pos = 0; pos < KCount; ++pos ) |
|
1241 { |
|
1242 const TGsChildRefData& KChildData = KChildIds[pos]; |
|
1243 |
|
1244 if ( KChildData.RecurrenceId() == aRecurrenceId ) |
|
1245 { |
|
1246 returnEntry = FetchEntryL(KChildData.ChildId()); |
|
1247 |
|
1248 break; |
|
1249 } |
|
1250 } |
|
1251 |
|
1252 return (returnEntry); |
|
1253 } |
|
1254 |
|
1255 /** Save the contents of iNextLocalUidValue and iNextAttachmentUid to the store |
|
1256 @internalComponent |
|
1257 */ |
|
1258 void CAgnEntryModel::ExternalizeNextUidValuesL() const |
|
1259 { |
|
1260 ExternalizeNextUidValuesL(StreamStore(), iModelStreamIdSet->NextLocalUidValueStreamId()); |
|
1261 } |
|
1262 |
|
1263 void CAgnEntryModel::ExternalizeNextUidValuesL(CStreamStore& aStreamStore, const TStreamId& aStreamId) const |
|
1264 { |
|
1265 RStoreWriteStream stream; |
|
1266 |
|
1267 stream.ReplaceLC(aStreamStore, aStreamId); |
|
1268 stream.WriteUint32L(iNextLocalUidValue); |
|
1269 stream.WriteUint32L(iNextAttachmentUid); |
|
1270 |
|
1271 stream.CommitL(); |
|
1272 |
|
1273 CleanupStack::PopAndDestroy(); |
|
1274 } |
|
1275 |
|
1276 void CAgnEntryModel::InternalizeNextUidValuesL() |
|
1277 { |
|
1278 RStoreReadStream in; |
|
1279 in.OpenLC(StreamStore(), iModelStreamIdSet->NextLocalUidValueStreamId()); |
|
1280 if (iCalConverter) |
|
1281 { |
|
1282 iCalConverter->InternalizeNextUidValuesL(in); |
|
1283 } |
|
1284 else |
|
1285 { |
|
1286 iNextLocalUidValue = in.ReadUint32L(); |
|
1287 iNextAttachmentUid = in.ReadUint32L(); |
|
1288 } |
|
1289 |
|
1290 CleanupStack::PopAndDestroy(&in); |
|
1291 } |
|
1292 |
|
1293 /** Sets whether or not to use buffered deletion. |
|
1294 |
|
1295 Buffered deletion means that when entries are deleted they are only |
|
1296 marked as being deleted in the internal memory buffer and the file |
|
1297 is not updated until either every entry in the buffer is marked as being |
|
1298 deleted or a commit/flush is called. |
|
1299 @param aSetting ETrue for buffered deletion, EFalse for non-buffered deletion. |
|
1300 @capability None |
|
1301 */ |
|
1302 void CAgnEntryModel::SetBufferedDeleting(TBool aSetting) |
|
1303 { |
|
1304 iEntryManager->SetBufferedDeleting(aSetting); |
|
1305 } |
|
1306 |
|
1307 /** |
|
1308 Flush out the entry store |
|
1309 */ |
|
1310 void CAgnEntryModel::FlushL() |
|
1311 { |
|
1312 iEntryManager->FlushBuffersL(); |
|
1313 } |
|
1314 |
|
1315 /** Resets any file specific data before opening a new calendar file. |
|
1316 @internalAll |
|
1317 */ |
|
1318 void CAgnEntryModel::Reset() |
|
1319 { |
|
1320 iFileId = 0; |
|
1321 |
|
1322 if (iModelStreamIdSet) |
|
1323 { |
|
1324 iModelStreamIdSet->Reset(); |
|
1325 } |
|
1326 |
|
1327 if (iEntryManager) |
|
1328 { |
|
1329 iEntryManager->Reset(); |
|
1330 } |
|
1331 |
|
1332 if (iSimpleEntryTable) |
|
1333 { |
|
1334 iSimpleEntryTable->Reset(); |
|
1335 } |
|
1336 } |
|
1337 |
|
1338 /** Save the entry manager and its data to the store |
|
1339 @internalAll |
|
1340 */ |
|
1341 void CAgnEntryModel::ExternalizeEntryManagerL() const |
|
1342 { |
|
1343 RStoreWriteStream stream; |
|
1344 stream.ReplaceLC(StreamStore(),iModelStreamIdSet->EntryManagerStreamId()); |
|
1345 stream << *iEntryManager; |
|
1346 stream.CommitL(); |
|
1347 CleanupStack::PopAndDestroy(); |
|
1348 } |
|
1349 |
|
1350 void CAgnEntryModel::InternalizeEntryManagerL() |
|
1351 { |
|
1352 RStoreReadStream in; |
|
1353 in.OpenLC(StreamStore(), iModelStreamIdSet->EntryManagerStreamId()); |
|
1354 in >> *iEntryManager; |
|
1355 |
|
1356 // Sanity Check here. If the entry stream set is empty, then the store stream ids should |
|
1357 // all be zero - reset them here to ensure that if the file has been partially corrupted, we can |
|
1358 // recover from this. |
|
1359 if ( iModelStreamIdSet->EntryStreamIdSet().Count() == 0 ) |
|
1360 { |
|
1361 iEntryManager->Reset(); |
|
1362 } |
|
1363 |
|
1364 CleanupStack::PopAndDestroy(&in); |
|
1365 } |
|
1366 |
|
1367 void CAgnEntryModel::ResetIndexes() |
|
1368 { |
|
1369 // Reset indexes, returns EFalse if file is empty |
|
1370 iSimpleEntryTable->Reset(); |
|
1371 iCategoryIndex->Reset(); |
|
1372 iAttachmentIndex->Reset(); |
|
1373 iModelStreamIdSet->EntryStreamIdSet().ResetIteratorToStart(); |
|
1374 if (iModelStreamIdSet->EntryStreamIdSet().Count() == 0) |
|
1375 { |
|
1376 iNumStreamsProcessed = 0; |
|
1377 } |
|
1378 } |
|
1379 |
|
1380 // This method is used for generating a filename for the index file from the |
|
1381 // calendar filename. |
|
1382 TBool CAgnEntryModel::GenerateIndexFileName(TFileName& aFileName) |
|
1383 { |
|
1384 aFileName = FileName(); |
|
1385 if ((aFileName.Length() + KIdxFilePostFixLength) > KMaxFileName) |
|
1386 { |
|
1387 iIndexFileIsDirty = ETrue; |
|
1388 iIndexFileIsPresent = EFalse; |
|
1389 return EFalse; |
|
1390 } |
|
1391 aFileName.Append(KIdxFilePostFix); |
|
1392 return ETrue; |
|
1393 } |
|
1394 |
|
1395 |
|
1396 // This method marks the index file as dirty (i.e. out of sync with the indices |
|
1397 // in RAM) by deleting it. A flag is kept internally to allow us to know that the |
|
1398 // file needs to be rebuilt and to no try to delete the file more than once. |
|
1399 void CAgnEntryModel::MarkIndexFileAsDirtyL() |
|
1400 { |
|
1401 if (iIndexFileIsDirty) |
|
1402 { |
|
1403 return; // the file is already marked as dirty |
|
1404 } |
|
1405 |
|
1406 TFileName idxfilename; |
|
1407 if (!GenerateIndexFileName(idxfilename)) |
|
1408 { |
|
1409 User::Leave(KErrBadName); |
|
1410 } |
|
1411 |
|
1412 TInt connectErr = iFs.Connect(); |
|
1413 User::LeaveIfError(connectErr); |
|
1414 |
|
1415 iFs.Delete(idxfilename); // ignore return as there is nothing we can do with it |
|
1416 |
|
1417 iIndexFileIsDirty = ETrue; |
|
1418 } |
|
1419 |
|
1420 // This method allows clients of the model to determine if the index file is |
|
1421 // dirty and therefore in need of being rewritten with the current data. |
|
1422 TBool CAgnEntryModel::IsIndexFileDirty() const |
|
1423 { |
|
1424 return iIndexFileIsDirty; |
|
1425 } |
|
1426 |
|
1427 TCalCollectionId CAgnEntryModel::CollectionId() const |
|
1428 { |
|
1429 return iAgnServerFile->CollectionId(); |
|
1430 } |
|
1431 |
|
1432 // This method reads the indices from the index file. |
|
1433 // It returns: |
|
1434 // ETrue - indices successfully read from file |
|
1435 // EFalse - indices not read from file (file may be missing, or there |
|
1436 // may have been errors trying to read the file. |
|
1437 // It Leaves when any of the index InternalizeL functions Leave. |
|
1438 // Overall description: |
|
1439 // 1. Attempt to open the file |
|
1440 // 2. If file is present InternalizeL all indices and return true |
|
1441 // 4. If no file, or errors in opening or streaming return false |
|
1442 TBool CAgnEntryModel::LoadIndexFileL() |
|
1443 { |
|
1444 TFileName idxfilename; |
|
1445 if (!GenerateIndexFileName(idxfilename)) |
|
1446 { |
|
1447 User::Leave(KErrBadName); |
|
1448 } |
|
1449 |
|
1450 TInt connectErr = iFs.Connect(); |
|
1451 User::LeaveIfError(connectErr); |
|
1452 |
|
1453 RFile idxFile; |
|
1454 TInt errReadIdx = idxFile.Open(iFs, idxfilename, EFileRead); |
|
1455 CleanupClosePushL(idxFile); |
|
1456 |
|
1457 if (errReadIdx == KErrNone) // we have a file |
|
1458 { |
|
1459 RFileReadStream idxStream; |
|
1460 idxStream.Attach(idxFile); |
|
1461 CleanupClosePushL(idxStream); |
|
1462 |
|
1463 TInt internalizeErr = KErrNone; |
|
1464 |
|
1465 TRAP(internalizeErr, iSimpleEntryTable->InternalizeL(idxStream, iTzRuleIndex)); |
|
1466 if (internalizeErr != KErrNone) |
|
1467 { |
|
1468 // clear any entries that may have been added to the table |
|
1469 // before leaving |
|
1470 iSimpleEntryTable->Reset(); |
|
1471 User::Leave(internalizeErr); |
|
1472 } |
|
1473 TRAP(internalizeErr, iCategoryIndex->InternalizeL(idxStream)); |
|
1474 if (internalizeErr != KErrNone) |
|
1475 { |
|
1476 // clear any entries in this index or the entry table |
|
1477 // before leaving |
|
1478 iSimpleEntryTable->Reset(); |
|
1479 iCategoryIndex->Reset(); |
|
1480 User::Leave(internalizeErr); |
|
1481 } |
|
1482 TRAP(internalizeErr, iAttachmentIndex->InternalizeL(idxStream)); |
|
1483 if (internalizeErr !=KErrNone) |
|
1484 { |
|
1485 // clear any entries in this index, the category index |
|
1486 // and the entry table before leaving |
|
1487 iAttachmentIndex->Reset(); |
|
1488 iSimpleEntryTable->Reset(); |
|
1489 iCategoryIndex->Reset(); |
|
1490 User::Leave(internalizeErr); |
|
1491 } |
|
1492 |
|
1493 CleanupStack::PopAndDestroy(2); //idxStream, idxFile |
|
1494 iIndexFileIsDirty = EFalse; |
|
1495 return ETrue; // we have successfully read the index file |
|
1496 } |
|
1497 else if (errReadIdx == KErrNotFound) |
|
1498 { |
|
1499 CleanupStack::PopAndDestroy(); // idxFile |
|
1500 iIndexFileIsDirty = ETrue; // the index file needs to be created/updated |
|
1501 iIndexFileIsPresent = EFalse; // so we won't try to find the file every time |
|
1502 // DoBuildIndexL() is called |
|
1503 return EFalse; // no file to read |
|
1504 } |
|
1505 |
|
1506 // if we get here, then there was an error reading the file for some |
|
1507 // reason other than it not being present. We'll mark it as DIRTY (i.e. delete it). |
|
1508 // MarkIndexFileAsDirtyL will try to delete the file if errors occur. |
|
1509 CleanupStack::PopAndDestroy(&idxFile); |
|
1510 MarkIndexFileAsDirtyL(); |
|
1511 iIndexFileIsPresent = EFalse; |
|
1512 return EFalse; |
|
1513 } |
|
1514 |
|
1515 |
|
1516 // This method attempts to save all indices to the index file. |
|
1517 // If any errors are encountered it will Leave. |
|
1518 // Clients of this method should TRAP the Leave and possibly |
|
1519 // mark the file as dirty or try to delete it. |
|
1520 void CAgnEntryModel::SaveIndexFileL() |
|
1521 { |
|
1522 TFileName idxfilename; |
|
1523 if (!GenerateIndexFileName(idxfilename)) |
|
1524 { |
|
1525 User::Leave(KErrBadName); |
|
1526 } |
|
1527 TInt connectErr = iFs.Connect(); |
|
1528 User::LeaveIfError(connectErr); |
|
1529 |
|
1530 RFile idxFile; |
|
1531 TInt errWriteIdx = idxFile.Replace(iFs, idxfilename, EFileWrite); |
|
1532 User::LeaveIfError(errWriteIdx); |
|
1533 CleanupClosePushL(idxFile); |
|
1534 |
|
1535 |
|
1536 RFileWriteStream idxStream; |
|
1537 idxStream.Attach(idxFile); |
|
1538 CleanupClosePushL(idxStream); |
|
1539 |
|
1540 iSimpleEntryTable->ExternalizeL(idxStream); |
|
1541 iCategoryIndex->ExternalizeL(idxStream); |
|
1542 iAttachmentIndex->ExternalizeL(idxStream); |
|
1543 |
|
1544 CleanupStack::PopAndDestroy(2); //idxStream, idxFile |
|
1545 |
|
1546 iIndexFileIsDirty = EFalse; |
|
1547 } |
|
1548 |
|
1549 TBool CAgnEntryModel::DoLoadIndexFile() |
|
1550 { |
|
1551 // Check to see if we have a valid index file that we can read |
|
1552 TBool readPassed = EFalse; |
|
1553 TRAPD(idxErr, readPassed = LoadIndexFileL()); |
|
1554 if ((readPassed) && (idxErr == KErrNone)) |
|
1555 { |
|
1556 // We successfully read the prebuilt index. |
|
1557 return ETrue; |
|
1558 } |
|
1559 else |
|
1560 { |
|
1561 // something bad happened to the index file |
|
1562 // we need to delete it because it couldn't be read |
|
1563 // To ensure that it is deleted we need to mark the index |
|
1564 // file as "not dirty". |
|
1565 iIndexFileIsDirty = EFalse; |
|
1566 iIndexFileIsPresent = EFalse; |
|
1567 // trap the leave to keep things running, but there is nothing |
|
1568 // we can do if the file can't be deleted. |
|
1569 TRAP_IGNORE(MarkIndexFileAsDirtyL()); |
|
1570 } |
|
1571 return EFalse; |
|
1572 } |
|
1573 |
|
1574 |
|
1575 TInt CAgnEntryModel::DoIndexBuildStepL() |
|
1576 { |
|
1577 // Check to see if we have a valid index file that we can read |
|
1578 // before trying to build all the indices. |
|
1579 |
|
1580 if (iIndexFileIsPresent) |
|
1581 { |
|
1582 if (DoLoadIndexFile()) |
|
1583 { |
|
1584 // We successfully read the prebuilt index. |
|
1585 // There is no need to go any further. |
|
1586 // The 0 below indicates that there is nothing left to do. |
|
1587 return KAgnPercentageComplete; |
|
1588 } |
|
1589 } |
|
1590 // otherwise, there is no file or the file is dirty (out of sync), |
|
1591 // continue to build indexes |
|
1592 |
|
1593 TInt retVal = 0; |
|
1594 |
|
1595 FOREVER |
|
1596 { |
|
1597 TStreamId streamId(0); |
|
1598 |
|
1599 if ( ! iModelStreamIdSet->EntryStreamIdSet().At(streamId) ) // returns value in streamId |
|
1600 { |
|
1601 retVal = KAgnPercentageComplete; // indicate completion if not more streams |
|
1602 break; |
|
1603 } |
|
1604 |
|
1605 RStoreReadStream in; |
|
1606 in.OpenLC(StreamStore(), streamId); |
|
1607 |
|
1608 in.ReadInt8L(); // discard buffer type information |
|
1609 |
|
1610 if ( iCalConverter ) |
|
1611 { |
|
1612 // Read entry from a calendar file whose |
|
1613 // version is not the current one. |
|
1614 |
|
1615 iCalConverter->InternalizeEntriesL(in); |
|
1616 } |
|
1617 else |
|
1618 { |
|
1619 // Read entry from a calendar file - current version |
|
1620 const TUint8 KCount = in.ReadUint8L(); |
|
1621 |
|
1622 CAgnEntry* entry = NULL; |
|
1623 for ( TInt ii = 0; ii < KCount; ++ii ) |
|
1624 { |
|
1625 entry = CAgnEntry::NewL(in); |
|
1626 CleanupStack::PushL(entry); |
|
1627 |
|
1628 UpdateIndexL(*entry, NULL, EBuildIndex); |
|
1629 CleanupStack::PopAndDestroy(entry); |
|
1630 } |
|
1631 } |
|
1632 |
|
1633 CleanupStack::PopAndDestroy(); // in |
|
1634 |
|
1635 iSimpleEntryTable->Commit(); |
|
1636 |
|
1637 if ( ! iModelStreamIdSet->EntryStreamIdSet().Next() ) |
|
1638 { |
|
1639 retVal = KAgnPercentageComplete; // no more streams to process |
|
1640 break; |
|
1641 } |
|
1642 |
|
1643 // check iNumStreamsProcessed is valid - this number is used to calculate percentage complete |
|
1644 ++iNumStreamsProcessed; |
|
1645 __ASSERT_ALWAYS(iNumStreamsProcessed <= iModelStreamIdSet->EntryStreamIdSet().Count(), User::Leave(KErrCorrupt)); |
|
1646 |
|
1647 // After every second stream is processed, calculate the percentage complete and return |
|
1648 if ( iNumStreamsProcessed % 2 == 0 ) |
|
1649 { |
|
1650 // coverity[check_return] coverity[unchecked_value] |
|
1651 TInt percentage = (iNumStreamsProcessed * KAgnPercentageComplete) / iModelStreamIdSet->EntryStreamIdSet().Count(); |
|
1652 retVal = (percentage < 1 ? 1 : percentage); // percentage complete must be at least 1 (returning 0 indicates index building complete) |
|
1653 break; |
|
1654 } |
|
1655 } |
|
1656 |
|
1657 if (retVal == KAgnPercentageComplete) |
|
1658 { |
|
1659 // A return value of 0 indicates that the indexes have been |
|
1660 // completely built. We will save them to file now so that |
|
1661 // no future errors will cause this information to need to |
|
1662 // be built again. |
|
1663 TRAPD (saveErr, SaveIndexFileL()); |
|
1664 if (saveErr != KErrNone) |
|
1665 { |
|
1666 // We couldn't save the index file, so we'll mark it as dirty |
|
1667 TRAPD(ignore,MarkIndexFileAsDirtyL()); |
|
1668 User::LeaveIfError(ignore); |
|
1669 } |
|
1670 } |
|
1671 |
|
1672 return (retVal); |
|
1673 } |
|
1674 |
|
1675 |
|
1676 void CAgnEntryModel::BuildIndexCompleteL() |
|
1677 { |
|
1678 if ( iCalConverter ) |
|
1679 { |
|
1680 RestoreCategoriesL(); |
|
1681 iCalConverter->CompleteConversionL(); |
|
1682 delete iCalConverter; |
|
1683 iCalConverter = NULL; |
|
1684 } |
|
1685 |
|
1686 CreateAlarmForServerL(); |
|
1687 iAlarm->DeleteAllAlarms(); |
|
1688 iAlarm->FindAndQueueNextAlarmL(EFalse); |
|
1689 } |
|
1690 |
|
1691 |
|
1692 TInt CAgnEntryModel::MatchExactText(const TDesC& aTextField, const TDesC& aSearchText) |
|
1693 { |
|
1694 return aTextField.Match(aSearchText); |
|
1695 } |
|
1696 |
|
1697 TInt CAgnEntryModel::MatchFoldedText(const TDesC& aTextField, const TDesC& aSearchText) |
|
1698 { |
|
1699 return aTextField.MatchC(aSearchText); |
|
1700 } |
|
1701 |
|
1702 TBool CAgnEntryModel::MatchSearchTextL(MatchTextFnPtr aMatchTextFunction, CAgnEntry& aEntry, const TDesC& aSearchText, const TAgnFilter& aFilter) |
|
1703 { |
|
1704 // always search summary |
|
1705 TInt pos = aMatchTextFunction(aEntry.Summary(), aSearchText); |
|
1706 |
|
1707 if ( pos == KErrNotFound && aFilter.IsEntryLocationSearched() ) |
|
1708 { |
|
1709 pos = aMatchTextFunction(aEntry.Location(), aSearchText); |
|
1710 } |
|
1711 if ( pos == KErrNotFound && aFilter.IsEntryDescriptionSearched() ) |
|
1712 { |
|
1713 pos = aMatchTextFunction(aEntry.Description(), aSearchText); |
|
1714 } |
|
1715 |
|
1716 CAgnAttendee* organizer = aEntry.Organizer(); |
|
1717 const TInt KNumAttendees = aEntry.AttendeeCount(); |
|
1718 |
|
1719 if ( organizer && pos == KErrNotFound ) |
|
1720 { |
|
1721 if ( aFilter.IsOrganizerAddressSearched() ) |
|
1722 { |
|
1723 pos = aMatchTextFunction(organizer->Address(), aSearchText); |
|
1724 } |
|
1725 if ( pos == KErrNotFound && aFilter.IsOrganizerSentByAddressSearched() ) |
|
1726 { |
|
1727 pos = aMatchTextFunction(organizer->SentBy(), aSearchText); |
|
1728 } |
|
1729 if ( pos == KErrNotFound && aFilter.IsOrganizerCommonNameSearched() ) |
|
1730 { |
|
1731 pos = aMatchTextFunction(organizer->CommonName(), aSearchText); |
|
1732 } |
|
1733 } |
|
1734 |
|
1735 for ( TInt i = 0; pos == KErrNotFound && i < KNumAttendees; ++i) |
|
1736 { |
|
1737 CAgnAttendee& attendee = aEntry.FetchAttendee(i); |
|
1738 |
|
1739 if ( aFilter.IsAttendeeAddressSearched() ) |
|
1740 { |
|
1741 pos = aMatchTextFunction(attendee.Address(), aSearchText); |
|
1742 } |
|
1743 if ( pos == KErrNotFound && aFilter.IsAttendeeSentByAddressSearched() ) |
|
1744 { |
|
1745 pos = aMatchTextFunction(attendee.SentBy(), aSearchText); |
|
1746 } |
|
1747 if ( pos == KErrNotFound && aFilter.IsAttendeeCommonNameSearched() ) |
|
1748 { |
|
1749 pos = aMatchTextFunction(attendee.CommonName(), aSearchText); |
|
1750 } |
|
1751 } |
|
1752 |
|
1753 return (pos >= 0); |
|
1754 } |
|
1755 |
|
1756 TBool CAgnEntryModel::MatchFullEntryL(const TAgnEntryId& aEntryId, const TFindInstanceParams& aSearchParams) |
|
1757 { |
|
1758 TBool match(ETrue); |
|
1759 if(aSearchParams.iSearchString.Length() > 0) |
|
1760 { |
|
1761 CAgnEntry* entry = FetchEntryL(aEntryId); |
|
1762 __ASSERT_ALWAYS(entry, User::Leave(KErrNotFound)); |
|
1763 |
|
1764 CleanupStack::PushL(entry); |
|
1765 if(!MatchSearchTextL(*entry, aSearchParams.iSearchString, aSearchParams.iFilter)) |
|
1766 { |
|
1767 match = EFalse; |
|
1768 } |
|
1769 CleanupStack::PopAndDestroy(entry); |
|
1770 } |
|
1771 return match; |
|
1772 } |
|
1773 |
|
1774 TBool CAgnEntryModel::MatchSearchTextL(CAgnEntry& aEntry, const TDesC& aSearchText, const TAgnFilter& aFilter) |
|
1775 { |
|
1776 TBool matchText = ETrue; |
|
1777 |
|
1778 if ( aSearchText.Length() > 0 ) |
|
1779 { |
|
1780 TBuf<256> searchString; |
|
1781 _LIT(KWildCard, "*"); |
|
1782 searchString.Append(KWildCard); |
|
1783 searchString.Append(aSearchText); |
|
1784 searchString.Append(KWildCard); |
|
1785 |
|
1786 if ( aEntry.Summary() == KNullDesC && aEntry.SummaryStreamId() != KNullStreamId ) |
|
1787 { |
|
1788 HBufC* summary = RestoreTextL(aEntry.SummaryStreamId()); |
|
1789 aEntry.SetSummary(summary); |
|
1790 } |
|
1791 |
|
1792 // load description if required |
|
1793 if ( aEntry.Description() == KNullDesC && aEntry.DescriptionStreamId() != KNullStreamId && aFilter.IsEntryDescriptionSearched() ) |
|
1794 { |
|
1795 HBufC* description = RestoreTextL(aEntry.DescriptionStreamId()); |
|
1796 aEntry.SetDescription(description); |
|
1797 } |
|
1798 |
|
1799 MatchTextFnPtr matchFn = &MatchFoldedText; |
|
1800 if ( aFilter.IsExactTextOnlySearch() ) |
|
1801 { |
|
1802 matchFn = &MatchExactText; |
|
1803 } |
|
1804 |
|
1805 matchText = MatchSearchTextL(matchFn, aEntry, searchString, aFilter); |
|
1806 } |
|
1807 |
|
1808 return (matchText); |
|
1809 } |
|
1810 |
|
1811 void CAgnEntryModel::FindInstancesL(CArrayFix<TAgnSortInstance>& aInstances, const TFindInstanceParams& aParameters) |
|
1812 { |
|
1813 iExtractor->FindInstancesL(aInstances, aParameters); |
|
1814 |
|
1815 #if defined (__CAL_INSTANCE_LOGGING__) || (__CAL_VERBOSE_LOGGING__) |
|
1816 LogInstanceSearchL(aParameters, aInstances); |
|
1817 #endif |
|
1818 } |
|
1819 |
|
1820 void CAgnEntryModel::LogInstanceSearchL(const TFindInstanceParams& aParameters, const CArrayFix<TAgnSortInstance>& aInstances) const |
|
1821 { |
|
1822 TAgnFilter iFilter; |
|
1823 TTime dbgStartTime; |
|
1824 TTime iEndTime; |
|
1825 TBuf<KAgnMaxSearchStringLength> iSearchString; |
|
1826 |
|
1827 TAgnFilter debugFilter = aParameters.iFilter; |
|
1828 |
|
1829 TBuf<64> filterBuf; |
|
1830 |
|
1831 if (debugFilter.AreTimedApptsIncluded() && |
|
1832 debugFilter.AreRemindersIncluded() && |
|
1833 debugFilter.AreEventsIncluded() && |
|
1834 debugFilter.AreAnnivsIncluded() && |
|
1835 debugFilter.AreCompletedTodosIncluded() && |
|
1836 debugFilter.AreIncompletedTodosIncluded() ) |
|
1837 { |
|
1838 filterBuf.Copy(_L("All entries")); |
|
1839 } |
|
1840 else |
|
1841 { |
|
1842 if (debugFilter.AreTimedApptsIncluded()) |
|
1843 { |
|
1844 filterBuf.Append(_L("Appts,")); |
|
1845 } |
|
1846 if (debugFilter.AreRemindersIncluded()) |
|
1847 { |
|
1848 filterBuf.Append(_L("Reminders,")); |
|
1849 } |
|
1850 if (debugFilter.AreEventsIncluded()) |
|
1851 { |
|
1852 filterBuf.Append(_L("Events,")); |
|
1853 } |
|
1854 if (debugFilter.AreAnnivsIncluded()) |
|
1855 { |
|
1856 filterBuf.Append(_L("Annivs,")); |
|
1857 } |
|
1858 if (debugFilter.AreCompletedTodosIncluded() && debugFilter.AreIncompletedTodosIncluded()) |
|
1859 { |
|
1860 filterBuf.Append(_L("TODOs,")); |
|
1861 } |
|
1862 else if (debugFilter.AreIncompletedTodosIncluded()) |
|
1863 { |
|
1864 filterBuf.Append(_L("Incomplete TODOs,")); |
|
1865 } |
|
1866 else if (debugFilter.AreCompletedTodosIncluded()) |
|
1867 { |
|
1868 filterBuf.Append(_L("Complete TODOs,")); |
|
1869 } |
|
1870 } |
|
1871 |
|
1872 if (aParameters.iSearchString.Length() > 0) |
|
1873 { |
|
1874 TBuf<KAgnMaxSearchStringLength> searchBuf; |
|
1875 searchBuf.Copy(aParameters.iSearchString); |
|
1876 AgmDebug::DebugLog("Searching for text %S", &searchBuf); |
|
1877 } |
|
1878 |
|
1879 TBuf<KMinTTimeStrLength> startTimeBuf; |
|
1880 TBuf<KMinTTimeStrLength> endTimeBuf; |
|
1881 |
|
1882 AgmDebug::TTimeStrL(aParameters.iRangeStart.LocalL(),startTimeBuf); |
|
1883 AgmDebug::TTimeStrL(aParameters.iRangeEnd.LocalL(),endTimeBuf); |
|
1884 |
|
1885 const TInt KInstanceCount(aInstances.Count()); |
|
1886 |
|
1887 AgmDebug::DebugLog("FindInstancesL: Range: Start - %S, End - %S, Filter '%S'", &startTimeBuf, &endTimeBuf, &filterBuf); |
|
1888 AgmDebug::DebugLog("Found %d instances", KInstanceCount); |
|
1889 |
|
1890 for ( TInt i = 0; i < KInstanceCount; ++i ) |
|
1891 { |
|
1892 TBuf<KMinTTimeStrLength> instanceTimeBuf; |
|
1893 AgmDebug::TTimeStrL(aInstances[i].InstanceIdL().Date().LocalL(), instanceTimeBuf); |
|
1894 AgmDebug::DebugLog("Found instance: %S", &instanceTimeBuf); |
|
1895 } |
|
1896 |
|
1897 } |
|
1898 |
|
1899 void CAgnEntryModel::CreateAlarmForServerL() |
|
1900 { |
|
1901 if ( ! iAlarm ) |
|
1902 { |
|
1903 iAlarm = CAgnAlarm::NewL(this, NULL); |
|
1904 } |
|
1905 } |
|
1906 |
|
1907 /** |
|
1908 Get list of ids of alarmed entries in the next 60 days. |
|
1909 FindInstanceL is used to find instances of alarmed entries. |
|
1910 */ |
|
1911 void CAgnEntryModel::NextAlarmForServerL(const TTime& aNow, CArrayFixFlat<TAgnSortInstance>& aAlarmedIds) |
|
1912 { |
|
1913 if ( ! AgnDateTime::IsValidAgendaTTime(aNow) || ! iSimpleEntryTable || iAgnServerFile->IsFileDisabled()) |
|
1914 { |
|
1915 return; |
|
1916 } |
|
1917 |
|
1918 CArrayFixSeg<TAgnSortInstance>* dayInfoList = new(ELeave) CArrayFixSeg<TAgnSortInstance>(4); |
|
1919 CleanupStack::PushL(dayInfoList); |
|
1920 |
|
1921 TFindInstanceParams searchParams; |
|
1922 searchParams.iUndatedTodoTimeLocal = aNow; |
|
1923 searchParams.iFilter = TAgnFilter(CalCommon::EIncludeAppts|CalCommon::EIncludeReminder|CalCommon::EIncludeEvents| |
|
1924 CalCommon::EIncludeAnnivs|CalCommon::EIncludeIncompletedTodos|CalCommon::EIncludeAlarmedOnly, CalCommon::EFoldedTextSearch); |
|
1925 searchParams.iSearchString = KNullDesC(); |
|
1926 searchParams.iRangeStart.SetLocalL(AgnDateTime::ResetToMidnight(aNow) - TTimeIntervalDays(1)); // alarms can be up to 24 hours after start time so check this |
|
1927 searchParams.iRangeEnd.SetLocalL(aNow + TTimeIntervalDays(2)); |
|
1928 |
|
1929 iExtractor->FindInstancesL(*dayInfoList, searchParams); |
|
1930 TAgnDaySortKey sortKey(AgnDateTime::MaxDate(), searchParams.iUndatedTodoTimeLocal); |
|
1931 dayInfoList->Sort(sortKey); |
|
1932 |
|
1933 |
|
1934 searchParams.iFilter = TAgnFilter(CalCommon::EIncludeAppts|CalCommon::EIncludeReminder|CalCommon::EIncludeEvents| |
|
1935 CalCommon::EIncludeAnnivs|CalCommon::EIncludeIncompletedTodos|CalCommon::EIncludeAlarmedOnly| |
|
1936 CalCommon::EIncludeRptsNextInstanceOnly, CalCommon::EFoldedTextSearch); |
|
1937 |
|
1938 // check the next month if no alarmed instances found |
|
1939 const TTime KLimit = aNow + TTimeIntervalDays(60); |
|
1940 |
|
1941 while ( dayInfoList->Count() == 0 && searchParams.iRangeStart.LocalL() < KLimit ) |
|
1942 { |
|
1943 searchParams.iRangeStart.SetLocalL(searchParams.iRangeEnd.LocalL()); |
|
1944 searchParams.iRangeEnd.SetLocalL(searchParams.iRangeStart.LocalL() + TTimeIntervalDays(10)); |
|
1945 |
|
1946 iExtractor->FindInstancesL(*dayInfoList, searchParams); |
|
1947 TAgnDaySortKey sortKey1(AgnDateTime::MaxDate(), searchParams.iUndatedTodoTimeLocal); |
|
1948 dayInfoList->Sort(sortKey1); |
|
1949 } |
|
1950 |
|
1951 UpdateAlarmListL(aAlarmedIds, *dayInfoList, aNow); |
|
1952 |
|
1953 CleanupStack::PopAndDestroy(); //dayInfoList |
|
1954 } |
|
1955 |
|
1956 /** |
|
1957 Examine the contents of aDayInfoList and see if the any of the contained alarm instances should be added to |
|
1958 or replace the contents of aAlarmedIds |
|
1959 @internalComponent |
|
1960 */ |
|
1961 |
|
1962 void CAgnEntryModel::UpdateAlarmListL(CArrayFixFlat<TAgnSortInstance>& aAlarmedIds,CArrayFixSeg<TAgnSortInstance>& aDayInfoList, const TTime& aNow) |
|
1963 { |
|
1964 TTime nextAlarmLocal(AgnDateTime::MaxDate()); |
|
1965 |
|
1966 for ( TInt ii = aDayInfoList.Count() - 1; ii >= 0; --ii ) |
|
1967 { |
|
1968 TAgnSortInstance sortInstance = aDayInfoList[ii]; |
|
1969 |
|
1970 if ( sortInstance.SimpleEntry().Type() == CCalEntry::ETodo || sortInstance.iStartTimeLocal >= AgnDateTime::ResetToMidnight(aNow) ) |
|
1971 { |
|
1972 TTime alarmTimeLocal(sortInstance.InstanceAlarmDateTime()); |
|
1973 |
|
1974 if ( alarmTimeLocal <= aNow && sortInstance.SimpleEntry().RptDef() ) |
|
1975 { |
|
1976 TTime nextInstance; |
|
1977 |
|
1978 while ( sortInstance.SimpleEntry().RptDef()->NudgeNextInstanceL(sortInstance.InstanceDate(), nextInstance, ETrue) && nextInstance <= aNow) |
|
1979 { |
|
1980 sortInstance.SetL(nextInstance, aNow); |
|
1981 |
|
1982 if ( sortInstance.iStartTimeLocal < AgnDateTime::MaxDate() ) |
|
1983 { |
|
1984 alarmTimeLocal = sortInstance.InstanceAlarmDateTime(); |
|
1985 } |
|
1986 } |
|
1987 } |
|
1988 |
|
1989 if ( AgnDateTime::IsValidAgendaTTime(alarmTimeLocal) ) |
|
1990 { |
|
1991 if ( alarmTimeLocal > aNow && alarmTimeLocal < nextAlarmLocal ) |
|
1992 { |
|
1993 aAlarmedIds.Reset(); |
|
1994 aAlarmedIds.AppendL(sortInstance); |
|
1995 nextAlarmLocal = alarmTimeLocal; |
|
1996 } |
|
1997 else |
|
1998 { |
|
1999 if ( alarmTimeLocal == nextAlarmLocal ) |
|
2000 { |
|
2001 aAlarmedIds.AppendL(sortInstance); |
|
2002 } |
|
2003 } |
|
2004 } |
|
2005 } |
|
2006 } |
|
2007 } |
|
2008 |
|
2009 /** Schedules a list of alarms whose dateTime meets the following criteria: |
|
2010 alarmTime >= aCurrentTime AND alarmTime <= aCurrentTime + 30 days |
|
2011 @internalComponent |
|
2012 */ |
|
2013 void CAgnEntryModel::NextFewAlarmsForServerL(const TTime& aStartDateTime,const TTime& aEndDateTime, |
|
2014 CArrayFixFlat<TAgnSortInstance>& aAlarmedIds,const TInt aMaxNumberOfAlarms) |
|
2015 { |
|
2016 if(iAgnServerFile->IsFileDisabled()) |
|
2017 { |
|
2018 return; |
|
2019 } |
|
2020 if ( AgnDateTime::IsValidAgendaTTime(aStartDateTime) && AgnDateTime::IsValidAgendaTTime(aEndDateTime) && |
|
2021 aEndDateTime>=aStartDateTime ) |
|
2022 { |
|
2023 CArrayFixSeg<TAgnSortInstance>* dayInfoList = new(ELeave) CArrayFixSeg<TAgnSortInstance>(4); |
|
2024 CleanupStack::PushL(dayInfoList); |
|
2025 |
|
2026 TFindInstanceParams searchParams; |
|
2027 searchParams.iUndatedTodoTimeLocal = searchParams.iRangeStart.LocalL(); |
|
2028 searchParams.iFilter = TAgnFilter(CalCommon::EIncludeAppts|CalCommon::EIncludeReminder|CalCommon::EIncludeEvents| |
|
2029 CalCommon::EIncludeAnnivs|CalCommon::EIncludeIncompletedTodos|CalCommon::EIncludeAlarmedOnly| |
|
2030 CalCommon::EIncludeRptsNextInstanceOnly, CalCommon::EFoldedTextSearch); |
|
2031 searchParams.iSearchString = KNullDesC(); |
|
2032 searchParams.iRangeStart.SetLocalL(aStartDateTime - TTimeIntervalDays(1)); // alarms can be up to 24 hours after start time so check this |
|
2033 searchParams.iRangeEnd.SetLocalL(aEndDateTime); |
|
2034 |
|
2035 iExtractor->FindInstancesL(*dayInfoList, searchParams); |
|
2036 TAgnAlarmSortKey aKey; |
|
2037 dayInfoList->Sort(aKey); |
|
2038 |
|
2039 AddToAlarmListL(aAlarmedIds, *dayInfoList, aStartDateTime, aEndDateTime, aMaxNumberOfAlarms); |
|
2040 |
|
2041 CleanupStack::PopAndDestroy(dayInfoList); |
|
2042 } |
|
2043 } |
|
2044 |
|
2045 |
|
2046 void CAgnEntryModel::AddToAlarmListL(CArrayFixFlat<TAgnSortInstance>& aAlarmedIds,CArrayFixSeg<TAgnSortInstance>& aDayInfoList, const TTime& aStartDateTime, |
|
2047 const TTime& aEndDateTime,const TInt ) |
|
2048 // |
|
2049 // Examine the contents of aDayInfoList add to aAlarmedIds |
|
2050 // |
|
2051 { |
|
2052 const TInt KDayListCount(aDayInfoList.Count()); |
|
2053 for (TInt i = 0; i < KDayListCount; ++i) |
|
2054 { |
|
2055 const TAgnSortInstance KSortInstance = aDayInfoList[i]; |
|
2056 const TTime KAlarmInstance(KSortInstance.InstanceAlarmDateTime()); |
|
2057 |
|
2058 if (KAlarmInstance > aStartDateTime && KAlarmInstance <= aEndDateTime) |
|
2059 { |
|
2060 aAlarmedIds.AppendL(KSortInstance); |
|
2061 } |
|
2062 } |
|
2063 } |
|
2064 |
|
2065 void CAgnEntryModel::FindAndQueueNextFewAlarmsL() |
|
2066 { |
|
2067 if ( iAlarm ) |
|
2068 { |
|
2069 iAlarm->FindAndQueueNextFewAlarmsL(); |
|
2070 } |
|
2071 } |
|
2072 |
|
2073 #ifdef SYMBIAN_SYSTEM_STATE_MANAGEMENT |
|
2074 void CAgnEntryModel::DeleteAlarmsAndRequeueSessionAlarmL() |
|
2075 { |
|
2076 if ( iAlarm ) |
|
2077 { |
|
2078 iAlarm->DeleteAlarmsAndRequeueSessionAlarmL(); |
|
2079 } |
|
2080 } |
|
2081 #endif |
|
2082 |
|
2083 /** |
|
2084 Return next time (from aStartDate) on which an instance exists |
|
2085 @internalComponent |
|
2086 */ |
|
2087 void CAgnEntryModel::NextPossibleInstancesL(CArrayFix<TAgnSortInstance>& aInstances, const TFindInstanceParams& aSearchParams) const |
|
2088 { |
|
2089 iExtractor->NextPossibleInstancesL(aInstances, aSearchParams); |
|
2090 } |
|
2091 /** |
|
2092 Return previous time (from aStartDate) on which an instance exists |
|
2093 @internalComponent |
|
2094 */ |
|
2095 void CAgnEntryModel::PreviousPossibleInstancesL(CArrayFix<TAgnSortInstance>& aInstances, const TFindInstanceParams& aSearchParams) const |
|
2096 { |
|
2097 iExtractor->PreviousPossibleInstancesL(aInstances, aSearchParams); |
|
2098 } |
|
2099 |
|
2100 |
|
2101 // Place the uids of entries that have a last changed data greater than aDate and which meet the selection |
|
2102 // criteria specified in aFilter into the aUids array. |
|
2103 // |
|
2104 void CAgnEntryModel::GetEntryUidsSinceDateL(const TTime& aTime, RArray<TCalLocalUid>& aUniqueIdList) |
|
2105 { |
|
2106 iSimpleEntryTable->FindByLastModifiedDateUtcL(aTime, aUniqueIdList); |
|
2107 } |
|
2108 |
|
2109 |
|
2110 /** |
|
2111 Get the file ID of the currently open Agenda file. |
|
2112 This is unique to the file. |
|
2113 @capability None |
|
2114 */ |
|
2115 const TInt64& CAgnEntryModel::GetFileIdL() |
|
2116 { |
|
2117 if ( iFileId == 0 ) |
|
2118 { |
|
2119 RStoreReadStream in; |
|
2120 in.OpenLC(StreamStore(),iModelStreamIdSet->FileInformationStreamId()); |
|
2121 TInt64 fileId=0; |
|
2122 in >> fileId; |
|
2123 CleanupStack::PopAndDestroy(); //in |
|
2124 iFileId = fileId; |
|
2125 } |
|
2126 |
|
2127 return iFileId; |
|
2128 } |
|
2129 |
|
2130 |
|
2131 HBufC* CAgnEntryModel::RestoreTextL(const TStreamId& aStream) |
|
2132 { |
|
2133 CStreamStore& store = StreamStore(); |
|
2134 RStoreReadStream in; |
|
2135 in.OpenLC(store, aStream); |
|
2136 TInt textLength = in.ReadUint32L(); |
|
2137 HBufC* text = HBufC::NewL(in, textLength); |
|
2138 CleanupStack::PopAndDestroy(); //in |
|
2139 return text; |
|
2140 } |
|
2141 |
|
2142 TStreamId CAgnEntryModel::StoreTextL(const TDesC& aText) |
|
2143 { |
|
2144 CStreamStore& store = StreamStore(); |
|
2145 RStoreWriteStream out; |
|
2146 TStreamId id = out.CreateLC(store); |
|
2147 out.WriteUint32L(aText.Length()); |
|
2148 out << aText; |
|
2149 out.CommitL(); |
|
2150 CleanupStack::PopAndDestroy(); //out |
|
2151 return id; |
|
2152 } |
|
2153 |
|
2154 /* |
|
2155 * Updates the notes text stored in the specified stream. Null Descriptors are expected to be |
|
2156 * handled client side |
|
2157 */ |
|
2158 void CAgnEntryModel::UpdateTextL(const TDesC& aText, const TStreamId& aStream) |
|
2159 { |
|
2160 __ASSERT_DEBUG(aText.Length() != 0, Panic(EAgmNullDescriptor)); |
|
2161 |
|
2162 CStreamStore& store = StreamStore(); |
|
2163 RStoreWriteStream out; |
|
2164 out.ReplaceLC(store, aStream); |
|
2165 out.WriteUint32L(aText.Length()); |
|
2166 out << aText; |
|
2167 out.CommitL(); |
|
2168 CleanupStack::PopAndDestroy(); //out |
|
2169 } |
|
2170 |
|
2171 /* |
|
2172 * Deletes the specified stream holding notes data. |
|
2173 * |
|
2174 */ |
|
2175 void CAgnEntryModel::DeleteTextStreamL(const TStreamId& aStream) |
|
2176 { |
|
2177 StreamStore().DeleteL(aStream); |
|
2178 } |
|
2179 |
|
2180 |
|
2181 CAgnContent* CAgnEntryModel::RestoreAlarmActionL(const TStreamId& aStream) |
|
2182 { |
|
2183 CStreamStore& store = StreamStore(); |
|
2184 RStoreReadStream in; |
|
2185 in.OpenLC(store, aStream); |
|
2186 |
|
2187 CAgnContent* alarmAction = new (ELeave) CAgnContent; |
|
2188 CleanupStack::PushL(alarmAction); |
|
2189 in >> *alarmAction; |
|
2190 CleanupStack::Pop(alarmAction); |
|
2191 |
|
2192 CleanupStack::PopAndDestroy(); //in |
|
2193 return alarmAction; |
|
2194 } |
|
2195 |
|
2196 |
|
2197 TStreamId CAgnEntryModel::StoreAlarmActionL(const CAgnContent& aAlarmAction) |
|
2198 { |
|
2199 CStreamStore& store = StreamStore(); |
|
2200 RStoreWriteStream out; |
|
2201 TStreamId id = out.CreateLC(store); |
|
2202 out << aAlarmAction; |
|
2203 out.CommitL(); |
|
2204 CleanupStack::PopAndDestroy(); //out |
|
2205 |
|
2206 return id; |
|
2207 } |
|
2208 |
|
2209 /* |
|
2210 * Updates the rich alarm data stored in the specified stream. |
|
2211 */ |
|
2212 void CAgnEntryModel::UpdateAlarmActionL(const CAgnContent& aAlarmAction, const TStreamId& aStream) |
|
2213 { |
|
2214 CStreamStore& store = StreamStore(); |
|
2215 RStoreWriteStream out; |
|
2216 out.ReplaceLC(store, aStream); |
|
2217 out << aAlarmAction; |
|
2218 out.CommitL(); |
|
2219 CleanupStack::PopAndDestroy(); //out |
|
2220 } |
|
2221 |
|
2222 /* |
|
2223 * Deletes the specified stream holding rich alarm data. |
|
2224 * |
|
2225 */ |
|
2226 void CAgnEntryModel::DeleteAlarmActionStreamL(const TStreamId& aStream) |
|
2227 { |
|
2228 StreamStore().DeleteL(aStream); |
|
2229 } |
|
2230 |
|
2231 const TDesC& CAgnEntryModel::FileName() const |
|
2232 { |
|
2233 return iAgnServerFile->FileName(); |
|
2234 } |
|
2235 |
|
2236 void CAgnEntryModel::SetUpdateAlarmL(TBool aUpdateAlarm) |
|
2237 { |
|
2238 iUpdateAlarm = aUpdateAlarm; |
|
2239 |
|
2240 if ( iUpdateAlarm ) |
|
2241 { |
|
2242 iAlarm->FindAndQueueNextAlarmL(EFalse); |
|
2243 } |
|
2244 } |
|
2245 |
|
2246 |
|
2247 /** Commits any changes both to file and internally that have occurred to the model. |
|
2248 |
|
2249 This function does not empty the buffers. Call FlushL to do that. |
|
2250 @capability None |
|
2251 */ |
|
2252 void CAgnEntryModel::DoCommitL() |
|
2253 { |
|
2254 |
|
2255 if(!AgnServFile().IsBackupRestoreLock()) |
|
2256 { |
|
2257 iModelStreamIdSet->CommitL(StreamStore()); |
|
2258 StreamStore().CommitL(); |
|
2259 iSimpleEntryTable->Commit(); |
|
2260 iAttachmentIndex->CommitL(*iAgnServerFile); |
|
2261 } |
|
2262 |
|
2263 // Trigger compacting after a certain number of operations on the model |
|
2264 if(iOperationsCounter >= KCompactOperationsThreshold) |
|
2265 { |
|
2266 // Initiate synchronous compact |
|
2267 iAgnServerFile->CompactFileL(); |
|
2268 // Reset operations counter |
|
2269 iOperationsCounter=0; |
|
2270 } |
|
2271 } |
|
2272 |
|
2273 // Commits all changes to file. |
|
2274 void CAgnEntryModel::CommitL() |
|
2275 { |
|
2276 DoCommitL(); |
|
2277 ResetRollback(); |
|
2278 } |
|
2279 |
|
2280 // Called after multiple entries have been deleted. |
|
2281 // This can fail at any time and must roll back, so notification cannot happen until the changes are committed to file. |
|
2282 // This function does the commits then notifies, using the rollback array to find which entries have been deleted. |
|
2283 void CAgnEntryModel::CommitAndNotifyDeletesL(TAgnChangeFilter& aChangeFilter) |
|
2284 { |
|
2285 DoCommitL(); |
|
2286 iChangeFilter = &aChangeFilter; |
|
2287 |
|
2288 const TInt KDeleteCount = iDeleteRollbackArray.Count(); |
|
2289 for (TInt i = 0; i < KDeleteCount; ++i) |
|
2290 { |
|
2291 CAgnEntry* deletedEntry = iDeleteRollbackArray[i]; |
|
2292 NotifyingL(MCalChangeCallBack2::EChangeDelete, *deletedEntry, NULL); |
|
2293 |
|
2294 if(iTzRuleIndex) |
|
2295 { |
|
2296 //Remove the tz rule from tz rule index |
|
2297 //we have to do it after CAgnEntryModel::NotifyingL that is indirectly using the |
|
2298 //tz rule in aEntry. |
|
2299 iTzRuleIndex->RemoveTzRuleL(*deletedEntry); |
|
2300 } |
|
2301 } |
|
2302 |
|
2303 StreamStore().CommitL(); |
|
2304 ResetRollback(); |
|
2305 } |
|
2306 |
|
2307 // Add an entry to a rollback array. |
|
2308 // If aAdd is ETrue it is added to the add rollback array (for add operations) |
|
2309 // If aAdd is EFalse it is added to the delete rollback array (for delete operations) |
|
2310 void CAgnEntryModel::AppendRollbackArrayL(const CAgnEntry& aEntry, TBool aAdd) |
|
2311 { |
|
2312 if ( aAdd ) |
|
2313 { |
|
2314 iAddRollbackArray.AppendL(aEntry.EntryId()); |
|
2315 } |
|
2316 else |
|
2317 { |
|
2318 CAgnEntry* entryCopy = aEntry.CloneL(); |
|
2319 CleanupStack::PushL(entryCopy); |
|
2320 iDeleteRollbackArray.AppendL(entryCopy); |
|
2321 CleanupStack::Pop(entryCopy); |
|
2322 } |
|
2323 } |
|
2324 |
|
2325 // Reset rollback arrays |
|
2326 void CAgnEntryModel::ResetRollback() |
|
2327 { |
|
2328 iAddRollbackArray.Reset(); |
|
2329 iDeleteRollbackArray.ResetAndDestroy(); |
|
2330 } |
|
2331 |
|
2332 /** |
|
2333 Rollback indexes in RAM. This is done by deleting all added entries and re-adding all deleted entries |
|
2334 @internalComponent |
|
2335 */ |
|
2336 void CAgnEntryModel::RollbackIndexesL() |
|
2337 { |
|
2338 // delete all added entries |
|
2339 for ( TInt ii = iAddRollbackArray.Count() - 1; ii >= 0; --ii ) |
|
2340 { |
|
2341 const TAgnEntryId& KEntryIdToDelete = iAddRollbackArray[ii]; |
|
2342 |
|
2343 CAgnSimpleEntry* entryToDelete = iSimpleEntryTable->GetEntry(KEntryIdToDelete); |
|
2344 if(entryToDelete != NULL) |
|
2345 { |
|
2346 iSimpleEntryTable->DeleteEntry(KEntryIdToDelete); |
|
2347 } |
|
2348 |
|
2349 iAddRollbackArray.Remove(ii); |
|
2350 } |
|
2351 |
|
2352 iAddRollbackArray.Reset(); |
|
2353 |
|
2354 if(iTzRuleIndex) |
|
2355 { |
|
2356 //Rollback the reference count of tz rules in tz rule index |
|
2357 TRAPD(ret, iTzRuleIndex->RollBackL()); |
|
2358 if(ret != KErrNotReady) |
|
2359 { |
|
2360 User::LeaveIfError(ret); |
|
2361 } |
|
2362 } |
|
2363 |
|
2364 // re-add all deleted entries |
|
2365 for ( TInt ii = iDeleteRollbackArray.Count() - 1; ii >= 0; --ii ) |
|
2366 { |
|
2367 CAgnEntry* entryToAdd = iDeleteRollbackArray[ii]; |
|
2368 |
|
2369 // Check the entry has a guid hash |
|
2370 if ( entryToAdd->GsDataType() == CGsData::EParent && ! entryToAdd->GuidHash() ) |
|
2371 { |
|
2372 entryToAdd->SetGuidHash( GenerateHash8L(entryToAdd->Guid()) ); |
|
2373 } |
|
2374 |
|
2375 // If the entry has already been added, delete it to prevent an error from re-adding the same entry. |
|
2376 // This can happen if the delete operation fails at a certain point. |
|
2377 const TAgnEntryId& KEntryIdToAdd = entryToAdd->EntryId(); |
|
2378 if (iSimpleEntryTable->GetEntry(KEntryIdToAdd)) |
|
2379 { |
|
2380 iSimpleEntryTable->DeleteEntry(KEntryIdToAdd); |
|
2381 } |
|
2382 |
|
2383 if(iTzRuleIndex) |
|
2384 { |
|
2385 //Fetch back the tz rule |
|
2386 iTzRuleIndex->FetchTzRuleL(*entryToAdd); |
|
2387 } |
|
2388 |
|
2389 // re-add the entry |
|
2390 AddEntryToIndexesL(*entryToAdd); |
|
2391 |
|
2392 iDeleteRollbackArray.Remove(ii); |
|
2393 delete entryToAdd; |
|
2394 } |
|
2395 |
|
2396 iDeleteRollbackArray.Reset(); |
|
2397 |
|
2398 iAttachmentIndex->Rollback(); |
|
2399 } |
|
2400 |
|
2401 /** Reverts the model to the state it was in after CommitL() or RollbackL() was |
|
2402 last called. This reverts changes to the file and to the indexes held in RAM. |
|
2403 |
|
2404 This means that it deletes all entries which have been added, and reinstates |
|
2405 all entries which have been deleted. |
|
2406 |
|
2407 Note that this function is only called when an operation has failed. After this function is complete, there will be a leave in the place where |
|
2408 Rollback was called. |
|
2409 |
|
2410 @internalComponent |
|
2411 */ |
|
2412 void CAgnEntryModel::Rollback() |
|
2413 { |
|
2414 iEntryManager->Reset(); |
|
2415 |
|
2416 StreamStore().Revert(); |
|
2417 |
|
2418 TRAPD(ret,iModelStreamIdSet->RollbackL()); |
|
2419 __ASSERT_DEBUG(ret==KErrNone, Panic(EAgmErrRollbackFailed)); |
|
2420 |
|
2421 TRAP(ret,RollbackIndexesL()); |
|
2422 } |
|
2423 |
|
2424 |
|
2425 void CAgnEntryModel::NotifyingL(MCalChangeCallBack2::TChangeType aChangeType, CAgnEntry& aEntry, CAgnInstanceInfo* aOriginalEntry) |
|
2426 { |
|
2427 if ( iChangeFilter ) |
|
2428 { |
|
2429 if ( iChangeFilter->ChangeBroadcastEnabled() ) |
|
2430 { |
|
2431 NotifyChangeL((iChangeFilter->Session()), &aEntry, aChangeType, aOriginalEntry); |
|
2432 } |
|
2433 else |
|
2434 { |
|
2435 iChangeFilter->SetChangeMadeWhileDisabled(ETrue); |
|
2436 } |
|
2437 |
|
2438 if ( aEntry.Type() == CCalEntry::ETodo ) |
|
2439 { |
|
2440 iChangeFilter->SetPubSubChange(TAgnChangeFilter::ETodoChanged); |
|
2441 } |
|
2442 else |
|
2443 { |
|
2444 iChangeFilter->SetPubSubChange(TAgnChangeFilter::EEventChanged); |
|
2445 } |
|
2446 |
|
2447 NotifyPublishAndSubscribeL(*iChangeFilter); |
|
2448 } |
|
2449 } |
|
2450 |
|
2451 /* |
|
2452 Delete aEntry from the store. If it has a positive replicated count however then mark it as having |
|
2453 been deleted and update it instead. |
|
2454 @capability WriteUserData |
|
2455 @capability ReadUserData |
|
2456 */ |
|
2457 void CAgnEntryModel::DeleteEntryL(CAgnEntry& aEntry, TBool aCascadeDelete, TAgnChangeFilter* aChangeFilter) |
|
2458 { |
|
2459 #if defined (__CAL_ENTRY_LOGGING__) || (__CAL_VERBOSE_LOGGING__) |
|
2460 { |
|
2461 TBuf<KMaxGuidBufLength> guidBuf; |
|
2462 guidBuf.Copy(aEntry.Guid()); |
|
2463 AgmDebug::DebugLog("DeleteEntryL: Deleting entry with local UID=%d, GUID=%S", aEntry.LocalUid(), &guidBuf); |
|
2464 } |
|
2465 #endif |
|
2466 |
|
2467 iChangeFilter = aChangeFilter; |
|
2468 |
|
2469 if ( aCascadeDelete ) |
|
2470 { |
|
2471 if ( aEntry.GsDataType() == CGsData::EParent ) |
|
2472 { |
|
2473 _DBGLOG_ENTRY(AgmDebug::DebugLog("DeleteEntryL: Deleting children");) |
|
2474 |
|
2475 DeleteChildrenL(aEntry); |
|
2476 } |
|
2477 else |
|
2478 { |
|
2479 _DBGLOG_ENTRY(AgmDebug::DebugLog("DeleteEntryL: Updating Parent");) |
|
2480 UpdateParentL(aEntry); |
|
2481 } |
|
2482 } |
|
2483 |
|
2484 DoDeleteEntryL(aEntry); |
|
2485 |
|
2486 NotifyingL(MCalChangeCallBack2::EChangeDelete,aEntry, NULL); |
|
2487 |
|
2488 if ( iUpdateAlarm && aEntry.HasAlarm() ) |
|
2489 { |
|
2490 iAlarm->FindAndQueueNextAlarmL(EFalse); |
|
2491 iAlarm->DeleteEntriesAlarmL(aEntry.EntryId()); |
|
2492 } |
|
2493 |
|
2494 if(iChangeFilter && iTzRuleIndex) |
|
2495 { |
|
2496 //Remove the tz rule from tz rule index |
|
2497 //we have to do it after CAgnEntryModel::NotifyingL that is indirectly using the |
|
2498 //tz rule in aEntry. |
|
2499 iTzRuleIndex->RemoveTzRuleL(aEntry); |
|
2500 } |
|
2501 } |
|
2502 |
|
2503 |
|
2504 void CAgnEntryModel::DeleteChildrenL(CAgnEntry& aParent) |
|
2505 {//Get Child ids |
|
2506 __ASSERT_DEBUG(aParent.GsDataType() == CGsData::EParent, Panic(EAgmErrNotParentEntry)); |
|
2507 |
|
2508 CAgnEntry* parent = FetchEntryL(aParent.EntryId()); |
|
2509 if (parent != NULL) |
|
2510 { |
|
2511 CleanupStack::PushL(parent); |
|
2512 |
|
2513 const RArray<TGsChildRefData>& KIds = parent->ChildIds(); |
|
2514 // delete each child entry |
|
2515 for ( TInt i = KIds.Count() - 1; i >= 0; --i ) |
|
2516 { |
|
2517 const TCalLocalUid& KChildId = KIds[i].ChildId(); |
|
2518 CAgnEntry* childEntry = FetchEntryL(KChildId); // pass flag so as not to tell parent |
|
2519 if (childEntry) |
|
2520 { |
|
2521 CleanupStack::PushL(childEntry); |
|
2522 |
|
2523 DeleteEntryL(*childEntry, EFalse, iChangeFilter); // don't propogate the delete back to this parent |
|
2524 |
|
2525 CleanupStack::PopAndDestroy(childEntry); |
|
2526 } |
|
2527 |
|
2528 aParent.RemoveChildId(KChildId); |
|
2529 } |
|
2530 |
|
2531 CleanupStack::PopAndDestroy(parent); |
|
2532 } |
|
2533 } |
|
2534 |
|
2535 |
|
2536 void CAgnEntryModel::UpdateParentL(CAgnEntry& aChild) |
|
2537 { |
|
2538 // It should be used in server side so that the notification of updating a parent is not sent |
|
2539 __ASSERT_DEBUG(aChild.GsDataType() == CGsData::EChild, Panic(EAgmErrNotChildEntry)); |
|
2540 |
|
2541 // get parent and update |
|
2542 CAgnEntry* parentEntry = FetchEntryL(aChild.ParentId()); |
|
2543 __ASSERT_ALWAYS(parentEntry, User::Leave(KErrNotFound)); |
|
2544 CleanupStack::PushL(parentEntry); |
|
2545 |
|
2546 parentEntry->RemoveChildId(aChild.LocalUid()); |
|
2547 |
|
2548 UpdateEntryL(*parentEntry, iChangeFilter, EFalse); |
|
2549 |
|
2550 CleanupStack::PopAndDestroy(parentEntry); |
|
2551 } |
|
2552 |
|
2553 |
|
2554 /** |
|
2555 Delete aEntry from the store. If the entry is a todo then its id is removed from its |
|
2556 todo list. |
|
2557 */ |
|
2558 void CAgnEntryModel::DoDeleteEntryL(CAgnEntry& aEntry) |
|
2559 { |
|
2560 DeleteExternalAttributesL(aEntry); |
|
2561 |
|
2562 TAgnEntryId id = aEntry.EntryId(); |
|
2563 TStreamId streamId = iEntryManager->DeleteEntryL(id); |
|
2564 |
|
2565 if ( streamId != KNullStreamId ) |
|
2566 { |
|
2567 __ASSERT_DEBUG(streamId == aEntry.EntryId().StreamId(), Panic(EAgmErrWrongEntryDeleted)); |
|
2568 StreamStore().DeleteL(streamId); |
|
2569 iModelStreamIdSet->EntryStreamIdSet().DeleteL(streamId); |
|
2570 } |
|
2571 |
|
2572 if ( ! iEntryManager->BufferedDeleting() || iEntryManager->BufferHasBeenStored() ) // during tidying only commit when the buffer has been written |
|
2573 { |
|
2574 iEntryManager->StoreBuffersL(); |
|
2575 ExternalizeEntryManagerL(); |
|
2576 // Don't commit on delete. CommitL is called from CalInterimAPI after a number have been added. |
|
2577 } |
|
2578 |
|
2579 UpdateIndexL(aEntry, NULL, EDelete); |
|
2580 } |
|
2581 |
|
2582 |
|
2583 TBool CAgnEntryModel::EntryHasNoChildrenAndNoValidInstancesL(CAgnEntry& aEntry) const |
|
2584 { |
|
2585 TInt instances(1); |
|
2586 TInt exceptions(0); |
|
2587 if ( aEntry.RptDef() ) |
|
2588 { |
|
2589 // Purely based on repeat rule, does not include exceptions' count |
|
2590 instances = aEntry.RptDef()->InstanceCountL(); |
|
2591 const RArray<TAgnCalendarTime>* KExceptionList = aEntry.RptDef()->Exceptions(); |
|
2592 if (KExceptionList) |
|
2593 { |
|
2594 exceptions += KExceptionList->Count(); |
|
2595 } |
|
2596 |
|
2597 __ASSERT_ALWAYS(instances >= exceptions, User::Leave(KErrCorrupt)); |
|
2598 } |
|
2599 |
|
2600 TBool entryHasNoChild = ((aEntry.GsDataType() == CGsData::EChild) || aEntry.ChildIds().Count() == 0); |
|
2601 return (entryHasNoChild && instances == exceptions); |
|
2602 } |
|
2603 |
|
2604 /* |
|
2605 @capability ReadUserData |
|
2606 @capability WriteUserData |
|
2607 */ |
|
2608 void CAgnEntryModel::UpdateEntryL(CAgnEntry& aEntry, TAgnChangeFilter* aChangeFilter, TBool aDeleteChildren) |
|
2609 { |
|
2610 TAgnEntryId originalId = aEntry.EntryId(); |
|
2611 |
|
2612 if (originalId.IsNullId()) |
|
2613 { |
|
2614 _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: EntryId is null. Must be a newly created entry");) |
|
2615 |
|
2616 // Only parent entries can be updated |
|
2617 if(aEntry.GsDataType() != CGsData::EParent) |
|
2618 { |
|
2619 _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: KErrArgument: Only parent entries can be updated");) |
|
2620 User::Leave(KErrArgument); |
|
2621 } |
|
2622 |
|
2623 RPointerArray<CAgnEntry> entriesWithThisGuid; |
|
2624 CleanupResetAndDestroyPushL(entriesWithThisGuid); |
|
2625 FetchEntriesL(aEntry.Guid(), entriesWithThisGuid); |
|
2626 |
|
2627 // Only an existing entry with the same guid can be updated |
|
2628 if(entriesWithThisGuid.Count() == 0) |
|
2629 { |
|
2630 _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: KErrNotFound: Only an existing entry with the same guid can be updated");) |
|
2631 User::Leave(KErrNotFound); |
|
2632 } |
|
2633 |
|
2634 CAgnEntry* existingParent = entriesWithThisGuid[0]; |
|
2635 aEntry.SetLocalUid(existingParent->LocalUid()); |
|
2636 aEntry.SetEntryId(existingParent->EntryId()); |
|
2637 originalId = existingParent->EntryId(); |
|
2638 |
|
2639 _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: Setting Ids: LocalUid - %d, EntryId - %d", aEntry.LocalUid(),aEntry.EntryId().Value());) |
|
2640 CleanupStack::PopAndDestroy(&entriesWithThisGuid); |
|
2641 } |
|
2642 |
|
2643 //client server calls needs to be updated |
|
2644 iChangeFilter = aChangeFilter; |
|
2645 |
|
2646 if (EntryHasNoChildrenAndNoValidInstancesL(aEntry)) |
|
2647 { |
|
2648 _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: Deleting invalid entry - No children and no valid instances");) |
|
2649 DeleteEntryL(aEntry, EFalse, iChangeFilter); |
|
2650 return; |
|
2651 } |
|
2652 |
|
2653 if ( aDeleteChildren ) |
|
2654 { |
|
2655 _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: Deleting children");) |
|
2656 DeleteChildrenL(aEntry); |
|
2657 } |
|
2658 |
|
2659 CAgnEntry* oldEntry = FetchEntryL(originalId); |
|
2660 __ASSERT_DEBUG(oldEntry, User::Leave(KErrNotFound)); |
|
2661 CleanupStack::PushL(oldEntry); |
|
2662 TBool hadAlarm = EFalse; |
|
2663 if ( oldEntry ) |
|
2664 { |
|
2665 hadAlarm = oldEntry->HasAlarm(); |
|
2666 } |
|
2667 |
|
2668 CAgnInstanceInfo* instanceInfoBefore = CAgnInstanceInfo::NewLC(*oldEntry); |
|
2669 |
|
2670 TRAPD(ret, DoUpdateEntryL(aEntry, oldEntry)); |
|
2671 |
|
2672 if ( ret != KErrNone ) |
|
2673 { |
|
2674 aEntry.SetEntryId(originalId); |
|
2675 _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: DoUpdateEntryL failed: Leaving with error - %d",ret);) |
|
2676 User::Leave(ret); |
|
2677 } |
|
2678 |
|
2679 NotifyingL(MCalChangeCallBack2::EChangeModify, aEntry, instanceInfoBefore); |
|
2680 |
|
2681 CleanupStack::PopAndDestroy(instanceInfoBefore); |
|
2682 CleanupStack::PopAndDestroy(oldEntry); |
|
2683 |
|
2684 // Delete the old alarm and if a new alarm exists it will be added by findAndQueue |
|
2685 // If a todo is completed or an event updated, while alarm is snoozed, |
|
2686 // then the snoozed alarm has to be deleted separately, and deletion is not handled by findAndQueue |
|
2687 if (hadAlarm) |
|
2688 { |
|
2689 _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: Deleting the alarm on the existing entry");) |
|
2690 |
|
2691 iAlarm->DeleteEntriesAlarmL(aEntry.EntryId()); |
|
2692 } |
|
2693 if (iUpdateAlarm && aEntry.HasAlarm()) |
|
2694 { |
|
2695 iAlarm->FindAndQueueNextAlarmL(EFalse); |
|
2696 } |
|
2697 } |
|
2698 |
|
2699 /** Update an entry in the store. |
|
2700 @capability ReadUserData |
|
2701 */ |
|
2702 void CAgnEntryModel::DoUpdateEntryL(CAgnEntry& aEntry, CAgnEntry* aOldEntry) |
|
2703 { |
|
2704 TStreamId newStreamId; |
|
2705 UpdateExternalAttributesL(aEntry); |
|
2706 |
|
2707 if (aOldEntry) |
|
2708 { |
|
2709 DoUpdateAttachmentsL(aEntry, *aOldEntry); |
|
2710 } |
|
2711 |
|
2712 if(iTzRuleIndex) |
|
2713 { |
|
2714 __ASSERT_DEBUG(aOldEntry, Panic(EAgmErrNullPointer)); |
|
2715 iTzRuleIndex->UpdateTzRuleL(*aOldEntry, aEntry); |
|
2716 } |
|
2717 |
|
2718 TStreamId oldStreamId = iEntryManager->UpdateEntryL(aEntry, newStreamId); |
|
2719 |
|
2720 if ( oldStreamId != KNullStreamId ) |
|
2721 { |
|
2722 StreamStore().DeleteL(oldStreamId); |
|
2723 iModelStreamIdSet->EntryStreamIdSet().DeleteL(oldStreamId); |
|
2724 } |
|
2725 |
|
2726 if ( newStreamId != KNullStreamId ) |
|
2727 { |
|
2728 iModelStreamIdSet->EntryStreamIdSet().AddL(newStreamId); |
|
2729 } |
|
2730 |
|
2731 iEntryManager->StoreBuffersL(); |
|
2732 ExternalizeEntryManagerL(); |
|
2733 |
|
2734 UpdateIndexL(aEntry, aOldEntry, EUpdate); |
|
2735 } |
|
2736 |
|
2737 // Called when an entry is updated |
|
2738 // This compares the new entry with the old one to see if any attachments have changed drive (by calling CCalAttachmentFile::SetDrive). |
|
2739 void CAgnEntryModel::DoUpdateAttachmentsL(CAgnEntry& aNewEntry, CAgnEntry& aOldEntry) |
|
2740 {//This method will move the attachment to a different drive if it has been reset by the user. |
|
2741 const TInt KOldAttachmentCount = aOldEntry.AttachmentCount(); |
|
2742 const TInt KNewAttachmentCount = aNewEntry.AttachmentCount(); |
|
2743 |
|
2744 for (TInt oldEntryIndex = 0; oldEntryIndex < KOldAttachmentCount; ++oldEntryIndex) |
|
2745 { |
|
2746 CAgnAttachment& oldAttach = aOldEntry.Attachment(oldEntryIndex); |
|
2747 if (oldAttach.Type() == CCalContent::EDispositionInline && oldAttach.Uid() != 0) |
|
2748 { |
|
2749 CAgnAttachmentFile& oldAttachFile = static_cast<CAgnAttachmentFile&>(oldAttach); |
|
2750 TDriveName oldDrive = oldAttachFile.Drive(); |
|
2751 |
|
2752 for (TInt newEntryIndex = 0; newEntryIndex < KNewAttachmentCount; ++newEntryIndex) |
|
2753 { |
|
2754 CAgnAttachmentFile& newAttachFile = static_cast<CAgnAttachmentFile&>(aNewEntry.Attachment(newEntryIndex)); |
|
2755 if (newAttachFile.Uid() == oldAttach.Uid() && newAttachFile.Drive() != oldDrive) |
|
2756 { |
|
2757 HBufC* newfilename = oldAttachFile.FileName().AllocLC(); |
|
2758 newfilename->Des().Replace(0,2,newAttachFile.Drive()); |
|
2759 iAgnServerFile->MoveFileL(oldAttachFile.FileName(), newfilename->Des()); |
|
2760 CleanupStack::PopAndDestroy(newfilename); |
|
2761 |
|
2762 break; |
|
2763 } |
|
2764 } |
|
2765 } |
|
2766 } |
|
2767 } |
|
2768 |
|
2769 void CAgnEntryModel::MoveAttachmentToDriveL(CAgnAttachmentFile& aOldFileAttachment, CAgnAttachmentFile& aNewFileAttachment) |
|
2770 { |
|
2771 TParsePtrC parse(aOldFileAttachment.FileName()); |
|
2772 HBufC* fileName = GenerateFilenameLC(aNewFileAttachment.Drive(), parse.NameAndExt()); |
|
2773 TPtr pFilename(fileName->Des()); |
|
2774 iAgnServerFile->MoveFileL(aOldFileAttachment.FileName(), pFilename); |
|
2775 aNewFileAttachment.SetFileNameL(*fileName); |
|
2776 CleanupStack::PopAndDestroy(fileName); |
|
2777 |
|
2778 #if defined (__CAL_ATTACH_LOGGING__) || (__CAL_VERBOSE_LOGGING__) |
|
2779 HBufC8* attachId = aNewFileAttachment.ContentId().AllocLC(); |
|
2780 AgmDebug::DebugLog("Moving attachment to drive: Old filename: %S to New filename with drive: %S", &aOldFileAttachment.FileName(), &pFilename); |
|
2781 AgmDebug::DebugLog("Attachment: UId - %S, Old filename size - %d, New filename size - %d", attachId, aOldFileAttachment.Size(), aNewFileAttachment.Size()); |
|
2782 CleanupStack::PopAndDestroy(attachId); |
|
2783 #endif |
|
2784 } |
|
2785 |
|
2786 const CAgnSimpleEntry* CAgnEntryModel::GetSimpleEntryFromIndexes(const TAgnEntryId& aEntryId) |
|
2787 { |
|
2788 return iSimpleEntryTable->GetEntry(aEntryId); |
|
2789 } |
|
2790 |
|
2791 const CAgnSimpleEntry* CAgnEntryModel::GetSimpleEntryFromIndexes(TCalLocalUid aUniqueId) |
|
2792 { |
|
2793 return iSimpleEntryTable->GetEntry(aUniqueId); |
|
2794 } |
|
2795 |
|
2796 /** |
|
2797 Gets an entry based on its entry ID. |
|
2798 |
|
2799 @internalComponent |
|
2800 @capability ReadUserData |
|
2801 @param aId The entry ID of the entry to retrieve. |
|
2802 @return Pointer to the entry. |
|
2803 */ |
|
2804 CAgnEntry* CAgnEntryModel::FetchEntryL(const TAgnEntryId& aId) const |
|
2805 { |
|
2806 CAgnEntry* entry = iEntryManager->FetchEntryL(aId); |
|
2807 if (entry) |
|
2808 { |
|
2809 CleanupStack::PushL(entry); |
|
2810 |
|
2811 if(iTzRuleIndex) |
|
2812 { |
|
2813 iTzRuleIndex->FetchTzRuleL(*entry); |
|
2814 } |
|
2815 |
|
2816 _DBGLOG_ENTRY(AgmDebug::DebugLog("FetchEntryL: Fetched entry with Stream Id %d, Partial Id %d",aId.Value(), aId.PartialId());) |
|
2817 |
|
2818 if ( entry->GsDataType() == CGsData::EChild ) |
|
2819 { |
|
2820 _DBGLOG_ENTRY(AgmDebug::DebugLog("FetchEntryL: Entry fetched is a child entry, Parent Id - %d", entry->ParentId());) |
|
2821 |
|
2822 // if a child entry has been fetched, get the recurrence ID and range stored with the parent |
|
2823 CAgnEntry* parent = FetchEntryL(entry->ParentId()); |
|
2824 |
|
2825 __ASSERT_ALWAYS(parent, User::Leave(KErrCorrupt)); // child without parent entry in DB! |
|
2826 CleanupStack::PushL(parent); |
|
2827 |
|
2828 entry->SetRecurrenceIdFromParentL(*parent); |
|
2829 |
|
2830 CleanupStack::PopAndDestroy(parent); |
|
2831 } |
|
2832 |
|
2833 _DBGLOG_ENTRY(AgmDebug::DebugLogEntryL(*entry, EDumpEntryAll);) |
|
2834 CleanupStack::Pop(entry); |
|
2835 } |
|
2836 return (entry); |
|
2837 } |
|
2838 |
|
2839 /** |
|
2840 Gets an entry based on its unique ID. |
|
2841 |
|
2842 @internalComponent |
|
2843 @capability ReadUserData |
|
2844 @param aId The unique ID of the entry to retrieve. |
|
2845 @return Pointer to the entry. |
|
2846 */ |
|
2847 CAgnEntry* CAgnEntryModel::FetchEntryL(TCalLocalUid aUniqueId) const |
|
2848 { |
|
2849 _DBGLOG_ENTRY(AgmDebug::DebugLog("FetchEntryL: Attempting to fetch simple entry with LocalUid='%d'", aUniqueId);) |
|
2850 |
|
2851 // find entry in indexes to get the entry ID |
|
2852 CAgnSimpleEntry* simpleEntry = iSimpleEntryTable->GetEntry(aUniqueId); |
|
2853 CAgnEntry* entry = NULL; |
|
2854 |
|
2855 if ( simpleEntry ) |
|
2856 { |
|
2857 _DBGLOG_ENTRY(AgmDebug::DebugLog("FetchEntryL: Entry found, fetching the full entry with Local ID %d",simpleEntry->EntryId().Value());) |
|
2858 |
|
2859 // fetch the full entry from the entry ID |
|
2860 entry = FetchEntryL(simpleEntry->EntryId()); |
|
2861 } |
|
2862 #if defined (__CAL_ENTRY_LOGGING__) || (__CAL_VERBOSE_LOGGING__) |
|
2863 else |
|
2864 { |
|
2865 AgmDebug::DebugLog("FetchEntryL: Entry not found"); |
|
2866 } |
|
2867 #endif |
|
2868 |
|
2869 |
|
2870 return ( entry ); |
|
2871 } |
|
2872 |
|
2873 /** |
|
2874 Gets entries based on GUID. |
|
2875 |
|
2876 @internalComponent |
|
2877 @capability ReadUserData |
|
2878 @param aGuid The GUID of the entry to retrieve |
|
2879 @param aList The list of CAgnEntry objects |
|
2880 */ |
|
2881 void CAgnEntryModel::FetchEntriesL(const TDesC8& aGuid, RPointerArray<CAgnEntry>& aList) const |
|
2882 { |
|
2883 CAgnEntry* parentEntry = FetchEntryL(aGuid); |
|
2884 |
|
2885 #if defined (__CAL_ENTRY_LOGGING__) || (__CAL_VERBOSE_LOGGING__) |
|
2886 TBuf<KMaxGuidBufLength> guidBuf; |
|
2887 guidBuf.Copy(aGuid); |
|
2888 AgmDebug::DebugLog("FetchEntriesL: Using GUID='%S'", &guidBuf); |
|
2889 #endif |
|
2890 |
|
2891 if ( parentEntry ) |
|
2892 { |
|
2893 #if defined (__CAL_ENTRY_LOGGING__) || (__CAL_VERBOSE_LOGGING__) |
|
2894 TBuf<KMaxGuidBufLength> guidBuf; |
|
2895 guidBuf.Copy(aGuid); |
|
2896 AgmDebug::DebugLog("FetchEntriesL: Found a parent entry with GUID='%S'", &guidBuf); |
|
2897 #endif |
|
2898 |
|
2899 CleanupStack::PushL(parentEntry); |
|
2900 aList.AppendL(parentEntry); |
|
2901 CleanupStack::Pop(parentEntry); |
|
2902 |
|
2903 // Fetch the children and add them to the array |
|
2904 const RArray<TGsChildRefData>& KIds = parentEntry->ChildIds(); |
|
2905 |
|
2906 const TInt KCount = KIds.Count(); |
|
2907 |
|
2908 _DBGLOG_ENTRY(AgmDebug::DebugLog("FetchEntriesL: Parent entry has %d child(ren)", KCount);) |
|
2909 |
|
2910 for ( TInt i = 0; i < KCount; ++i ) |
|
2911 { |
|
2912 CAgnEntry* childEntry = FetchEntryL(KIds[i].ChildId()); // pass flag so as not to tell parent to update its child list |
|
2913 if (childEntry) |
|
2914 { |
|
2915 CleanupStack::PushL(childEntry); |
|
2916 aList.AppendL(childEntry); |
|
2917 CleanupStack::Pop(childEntry); |
|
2918 } |
|
2919 } |
|
2920 } // parentEntry |
|
2921 } |
|
2922 |
|
2923 |
|
2924 CAgnEntry* CAgnEntryModel::FetchEntryL(const TDesC8& aGuid, const TAgnCalendarTime& aRecurrenceId) const |
|
2925 { |
|
2926 CAgnEntry* returnEntry = NULL; |
|
2927 CAgnEntry* parentEntry = FetchEntryL(aGuid); |
|
2928 |
|
2929 if ( parentEntry ) |
|
2930 { |
|
2931 CleanupStack::PushL(parentEntry); |
|
2932 returnEntry = FindChildFromParentL(*parentEntry, aRecurrenceId); |
|
2933 CleanupStack::PopAndDestroy(parentEntry); |
|
2934 } |
|
2935 |
|
2936 return returnEntry; |
|
2937 } |
|
2938 |
|
2939 TBool CAgnEntryModel::AreIndexesBuilt() const |
|
2940 { |
|
2941 if (iAgnServerFile) |
|
2942 { |
|
2943 return iAgnServerFile->AreIndexesBuilt(); |
|
2944 } |
|
2945 return EFalse; |
|
2946 } |
|
2947 |
|
2948 |
|
2949 void CAgnEntryModel::RestoreCategoriesL() |
|
2950 { |
|
2951 if ( iCalConverter ) |
|
2952 { |
|
2953 iCalConverter->InternalizeCategoriesL(); |
|
2954 } |
|
2955 } |
|
2956 |
|
2957 void CAgnEntryModel::OpenAttachmentFileL(RFile& aFile, TInt aAttachmentUid) const |
|
2958 { |
|
2959 const CAgnAttachmentIndexItem* const item = iAttachmentIndex->Attachment(aAttachmentUid); |
|
2960 // item is owned by iAttachmentIndex |
|
2961 |
|
2962 if ( item ) |
|
2963 { |
|
2964 iAgnServerFile->OpenFileL(aFile, item->FileName()); |
|
2965 } |
|
2966 else |
|
2967 { |
|
2968 User::Leave(KErrNotFound); |
|
2969 } |
|
2970 } |
|
2971 |
|
2972 |
|
2973 void CAgnEntryModel::CreateNewFileL(RFile& aFile, const TDesC& aFileName) |
|
2974 { |
|
2975 iAgnServerFile->CreateNewFileL(aFile, aFileName); |
|
2976 } |
|
2977 |
|
2978 // Called when a binary data attachment is stored. |
|
2979 // At this stage, a new file has been created, and the file handle returned to the client. |
|
2980 // The data is written to file from the client side, then this function is called to update the metadata of the entry containing the attachment. |
|
2981 void CAgnEntryModel::UpdateAttachmentDetailsL(TCalLocalUid aLocalUid, TInt aAttachmentIndex, const TDesC& aFileName, TInt aAttachmentSize) |
|
2982 { |
|
2983 _DBGLOG_ATTACH(AgmDebug::DebugLog("UpdateAttachmentDetailsL: Local Uid %d, Attachment Index %d, FileName %S, Attachment Size %d", aLocalUid, aAttachmentIndex, &aFileName, aAttachmentSize);) |
|
2984 |
|
2985 CAgnEntry* entry = FetchEntryL(aLocalUid); |
|
2986 |
|
2987 if ( entry ) |
|
2988 { |
|
2989 CleanupStack::PushL(entry); |
|
2990 |
|
2991 CAgnAttachmentFile* attachment = static_cast<CAgnAttachmentFile*>(&entry->Attachment(aAttachmentIndex)); |
|
2992 |
|
2993 if ( attachment && attachment->FileName().Length() <= KMaxDriveName) |
|
2994 { |
|
2995 attachment->SetFileNameL(aFileName); |
|
2996 attachment->SetSize(aAttachmentSize); |
|
2997 attachment->SetUid(iNextAttachmentUid); |
|
2998 ++iNextAttachmentUid; |
|
2999 UpdateEntryL(*entry, NULL, EFalse); |
|
3000 |
|
3001 ExternalizeNextUidValuesL(); |
|
3002 } |
|
3003 |
|
3004 CleanupStack::PopAndDestroy(entry); |
|
3005 } |
|
3006 } |
|
3007 |
|
3008 TInt CAgnEntryModel::TransferFileFromClientL(RFile& aAttachfileHandle, CAgnAttachmentFile& aAttachFile, CAgnEntry& aEntry, TBool aIsSameDrive) |
|
3009 { |
|
3010 RBuf originalFileName; |
|
3011 originalFileName.CreateL(KMaxFileName); |
|
3012 CleanupClosePushL(originalFileName); |
|
3013 aAttachfileHandle.FullName(originalFileName); |
|
3014 TInt size; |
|
3015 User::LeaveIfError(aAttachfileHandle.Size(size)); |
|
3016 |
|
3017 // Generate attachment filename |
|
3018 |
|
3019 TParsePtrC parseOriginalFile(originalFileName); |
|
3020 HBufC* fileName = GenerateFilenameLC(aAttachFile.FileName(), parseOriginalFile.NameAndExt()); |
|
3021 TPtr attachFilename(fileName->Des()); |
|
3022 |
|
3023 _DBGLOG_ATTACH(AgmDebug::DebugLog("TransferFileFromClientL: Transferring file from: %S of size %d to %S", &originalFileName, size, &attachFilename);) |
|
3024 if(aIsSameDrive) |
|
3025 { |
|
3026 User::LeaveIfError(aAttachfileHandle.Rename(attachFilename)); // move the file to calendar area |
|
3027 } |
|
3028 else |
|
3029 { |
|
3030 aAttachfileHandle.Close(); |
|
3031 iAgnServerFile->MoveFileL(originalFileName, attachFilename); |
|
3032 } |
|
3033 aAttachFile.SetFileNameL(attachFilename); |
|
3034 aAttachFile.SetSize(size); |
|
3035 aAttachFile.SetUid(iNextAttachmentUid++); |
|
3036 |
|
3037 // Don't call UpdateEntryL as we only need to update the Calendar db file |
|
3038 TRAPD(err, UpdateEntryL(aEntry, NULL, EFalse)); |
|
3039 if (err != KErrNone) |
|
3040 { |
|
3041 // if the entry failed to update, move the attachment back |
|
3042 if (aIsSameDrive) |
|
3043 { |
|
3044 User::LeaveIfError(aAttachfileHandle.Rename(originalFileName)); |
|
3045 } |
|
3046 else |
|
3047 { |
|
3048 iAgnServerFile->MoveFileL(fileName->Des(), originalFileName); |
|
3049 } |
|
3050 User::Leave(err); |
|
3051 } |
|
3052 |
|
3053 CleanupStack::PopAndDestroy(2, &originalFileName); |
|
3054 ExternalizeNextUidValuesL(); |
|
3055 return iNextAttachmentUid-1; |
|
3056 } |
|
3057 TInt CAgnEntryModel::MoveFileToServerL(TCalLocalUid aLocalUid, TInt aAttachmentIndex) |
|
3058 { |
|
3059 TInt ret = 0; |
|
3060 TRAPD(err, ret = DoMoveFileToServerL(aLocalUid, aAttachmentIndex)); |
|
3061 if(err != KErrNone) |
|
3062 { |
|
3063 iAttachmentFileHandle.Close(); |
|
3064 User::Leave(err); |
|
3065 } |
|
3066 return ret; |
|
3067 } |
|
3068 |
|
3069 TInt CAgnEntryModel::DoMoveFileToServerL(TCalLocalUid aLocalUid, TInt aAttachmentIndex) |
|
3070 { |
|
3071 _DBGLOG_ATTACH(AgmDebug::DebugLog("DoMoveFileToServerL: Local Uid %d, Attachment Index %d", aLocalUid, aAttachmentIndex);) |
|
3072 |
|
3073 TCalAttachmentUid attachUid(0); |
|
3074 |
|
3075 CAgnEntry* entry = FetchEntryL(aLocalUid); |
|
3076 __ASSERT_ALWAYS(entry, User::Leave(KErrCorrupt)); |
|
3077 CleanupStack::PushL(entry); |
|
3078 |
|
3079 CAgnAttachment& attach = entry->Attachment(aAttachmentIndex); |
|
3080 __ASSERT_ALWAYS(attach.Type() == CCalContent::EDispositionInline, User::Leave(KErrCorrupt)); |
|
3081 |
|
3082 CAgnAttachmentFile& attachFile = static_cast<CAgnAttachmentFile&>(attach); |
|
3083 __ASSERT_ALWAYS(attachFile.Drive().CompareF(KDefaultAttachmentDrive()),User::Leave(KErrCorrupt) );//Drive is the default one |
|
3084 |
|
3085 attachUid = TransferFileFromClientL(iAttachmentFileHandle,attachFile, *entry, EFalse); |
|
3086 |
|
3087 CleanupStack::PopAndDestroy(entry); |
|
3088 return attachUid; |
|
3089 } |
|
3090 |
|
3091 TInt CAgnEntryModel::TransferAttachmentFileToServerL(RFile& aFile, TCalLocalUid aLocalUid, TInt aAttachmentIndex) |
|
3092 { |
|
3093 _DBGLOG_ATTACH(AgmDebug::DebugLog("TransferAttachmentFileToServerL: Local Uid %d, Attachment Index %d", aLocalUid, aAttachmentIndex);) |
|
3094 |
|
3095 TCalAttachmentUid attachUid(0); |
|
3096 CAgnEntry* entry = FetchEntryL(aLocalUid); |
|
3097 __ASSERT_ALWAYS(entry, User::Leave(KErrCorrupt)); |
|
3098 CleanupStack::PushL(entry); |
|
3099 |
|
3100 CAgnAttachment& attach = entry->Attachment(aAttachmentIndex); |
|
3101 __ASSERT_ALWAYS(attach.Type() == CCalContent::EDispositionInline, User::Leave(KErrCorrupt)); |
|
3102 |
|
3103 CAgnAttachmentFile& attachFile = static_cast<CAgnAttachmentFile&>(attach); |
|
3104 |
|
3105 if(!attachFile.Drive().CompareF(KDefaultAttachmentDrive()))//Drive is the default one |
|
3106 { |
|
3107 attachUid = TransferFileFromClientL(aFile, attachFile, *entry, ETrue); |
|
3108 } |
|
3109 else |
|
3110 {//Client need to close the handle in order to move the original file to the drive specified. |
|
3111 User::LeaveIfError(iAttachmentFileHandle.Duplicate(aFile)); |
|
3112 } |
|
3113 |
|
3114 CleanupStack::PopAndDestroy(entry); |
|
3115 return attachUid; |
|
3116 } |
|
3117 |
|
3118 // Generate a filename for an attachment on the specified drive. |
|
3119 HBufC* CAgnEntryModel::GenerateFilenameLC(const TDesC& aDrive, const TDesC& aFileName) |
|
3120 { |
|
3121 _LIT(KCalDirectory, "\\"); |
|
3122 |
|
3123 // file name is "X:\\private\\10003a5b\\calendarfilename_a\\Y\\filename" |
|
3124 // where X is the drive specified (KDefaultAttachmentDrive if none is set) |
|
3125 // and Y is the folder number calculated from the attachment ID |
|
3126 const TInt KNumberOfAttachmentsPerFolder = 32; |
|
3127 //Restricting the Attachemnt folder name length to 2 chars, where it allows to add max0-99 folders |
|
3128 //by considering the KMaxFileName Length is allowed 220 Chars |
|
3129 const TInt KMaxNumOfAttachmentFolders = 100; |
|
3130 // 8 to cover attachment folder name and trailing number if there exists a same file name |
|
3131 const TInt KExtraFileNameLength = 8; |
|
3132 TInt fileNameLength = iAgnServerFile->FileName().Length() + KExtraFileNameLength + aFileName.Length(); |
|
3133 TPtrC fileNamePtr (aFileName); |
|
3134 if(fileNameLength > KMaxFileName) |
|
3135 { |
|
3136 const TInt KMinFileNameLength = 8; |
|
3137 fileNameLength = iAgnServerFile->FileName().Length() + KExtraFileNameLength + KMinFileNameLength; |
|
3138 if(fileNameLength > KMaxFileName) |
|
3139 { |
|
3140 User::Leave(KErrBadName); |
|
3141 } |
|
3142 else |
|
3143 { |
|
3144 fileNamePtr.Set(aFileName.Left(KMinFileNameLength - 2));//save 2 for trailling number |
|
3145 } |
|
3146 } |
|
3147 HBufC* fileName = HBufC::NewLC(fileNameLength); |
|
3148 TPtr folderNamePtr = fileName->Des(); |
|
3149 iAgnServerFile->GetAttachmentFolderNameL(folderNamePtr); |
|
3150 |
|
3151 // if the drive has been set already, set it on the filename |
|
3152 if ( aDrive.Length() >= 1 ) |
|
3153 { |
|
3154 folderNamePtr.Replace(0, 1, aDrive.Left(1)); |
|
3155 } |
|
3156 else |
|
3157 { |
|
3158 folderNamePtr.Replace(0, 1, KDefaultAttachmentDrive().Left(1)); |
|
3159 } |
|
3160 |
|
3161 const TInt KFolderNumber = iNextAttachmentUid / KNumberOfAttachmentsPerFolder; |
|
3162 if (KFolderNumber >= KMaxNumOfAttachmentFolders) |
|
3163 { |
|
3164 User::LeaveIfError(KErrDirFull); |
|
3165 } |
|
3166 folderNamePtr.AppendNum(KFolderNumber); |
|
3167 folderNamePtr.Append(KCalDirectory); |
|
3168 TPtr fullFileNamePtr(folderNamePtr); |
|
3169 fullFileNamePtr.Append(fileNamePtr); |
|
3170 TBool uniqueFilenameGenerated = EFalse; |
|
3171 TInt count = 0; |
|
3172 while ( ! uniqueFilenameGenerated ) |
|
3173 { |
|
3174 if ( !iAgnServerFile->FileExistsL(fullFileNamePtr) ) |
|
3175 { |
|
3176 uniqueFilenameGenerated = ETrue; |
|
3177 } |
|
3178 else |
|
3179 { |
|
3180 TParsePtrC parse(fileNamePtr); |
|
3181 fullFileNamePtr = folderNamePtr; |
|
3182 fullFileNamePtr.Append(parse.Name()); |
|
3183 fullFileNamePtr.AppendNum(count++); |
|
3184 fullFileNamePtr.Append(parse.Ext()); |
|
3185 } |
|
3186 } |
|
3187 |
|
3188 iAgnServerFile->CreateDirL(fullFileNamePtr); |
|
3189 return fileName; |
|
3190 } |
|
3191 |
|
3192 // Generate a filename for an attachment on the specified drive. |
|
3193 HBufC* CAgnEntryModel::GenerateRandomFilenameLC(const TDesC& aDrive) |
|
3194 { |
|
3195 _LIT(KCalDirectory, "\\"); |
|
3196 |
|
3197 // file name is "X:\\private\\10003a5b\\calendarfilename_a\\Y\\filename" |
|
3198 // where X is the drive specified (KDefaultAttachmentDrive if none is set) |
|
3199 // and Y is the folder number calculated from the attachment ID |
|
3200 const TInt KNumberOfAttachmentsPerFolder = 32; |
|
3201 const TInt KNumCharsInFileName = 8; |
|
3202 //Restricting the Attachemnt folder name length to 2 chars, where it allows to add max0-99 folders |
|
3203 //by considering the KMaxFileName Length is allowed 220 Chars |
|
3204 const TInt KMaxNumOfAttachmentFolders = 100; |
|
3205 |
|
3206 // 16 to cover attachment folder name and extension - could be 'foldername\\888\\filename.xxx' |
|
3207 const TInt KFileNameLength = iAgnServerFile->FileName().Length() + 32 + KNumCharsInFileName; |
|
3208 |
|
3209 HBufC* fileName = HBufC::NewLC(KFileNameLength); |
|
3210 TPtr fileNamePtr = fileName->Des(); |
|
3211 |
|
3212 iAgnServerFile->GetAttachmentFolderNameL(fileNamePtr); |
|
3213 |
|
3214 // if the drive has been set already, set it on the filename |
|
3215 if ( aDrive.Length() >= 1 ) |
|
3216 { |
|
3217 fileNamePtr.Replace(0, 1, aDrive.Left(1)); |
|
3218 } |
|
3219 else |
|
3220 { |
|
3221 fileNamePtr.Replace(0, 1, KDefaultAttachmentDrive().Left(1)); |
|
3222 } |
|
3223 |
|
3224 const TInt KFolderNumber = iNextAttachmentUid / KNumberOfAttachmentsPerFolder; |
|
3225 if (KFolderNumber >= KMaxNumOfAttachmentFolders) |
|
3226 { |
|
3227 User::LeaveIfError(KErrDirFull); |
|
3228 } |
|
3229 fileNamePtr.AppendNum(KFolderNumber); |
|
3230 fileNamePtr.Append(KCalDirectory); |
|
3231 |
|
3232 TBool uniqueFilenameGenerated = EFalse; |
|
3233 |
|
3234 TTime time; |
|
3235 time.UniversalTime(); |
|
3236 TInt64 seed = time.Int64() + iNextAttachmentUid; |
|
3237 |
|
3238 while ( ! uniqueFilenameGenerated ) |
|
3239 { |
|
3240 // Generate a random filename |
|
3241 for ( TInt i = 0; i < KNumCharsInFileName; ++i, ++seed ) |
|
3242 { |
|
3243 TChar randomChar = (Math::Rand(seed) % 26) + 'a'; |
|
3244 fileNamePtr.Append(randomChar); |
|
3245 } |
|
3246 |
|
3247 // check that there is not an existing filename with the same name (very unlikely!) |
|
3248 if ( iAgnServerFile->FileExistsL(fileNamePtr) ) |
|
3249 { |
|
3250 // Remove this file name from the descriptor if the file exists already, and generate a new name |
|
3251 fileNamePtr.SetLength(fileNamePtr.Length() - KNumCharsInFileName); |
|
3252 } |
|
3253 else |
|
3254 { |
|
3255 uniqueFilenameGenerated = ETrue; |
|
3256 } |
|
3257 } |
|
3258 |
|
3259 iAgnServerFile->CreateDirL(fileNamePtr); |
|
3260 return fileName; |
|
3261 } |
|
3262 |
|
3263 const RArray<TCalLocalUid>* CAgnEntryModel::GetEntriesWithAttachment(TCalAttachmentUid aAttachmentUid) const |
|
3264 { |
|
3265 _DBGLOG_ATTACH(AgmDebug::DebugLog("GetEntriesWithAttachment: Attachment Uid %d", aAttachmentUid);) |
|
3266 |
|
3267 const CAgnAttachmentIndexItem* const item = iAttachmentIndex->Attachment(aAttachmentUid); |
|
3268 |
|
3269 if ( item && item->Entries().Count()) |
|
3270 { |
|
3271 return &item->Entries(); |
|
3272 } |
|
3273 |
|
3274 return NULL; |
|
3275 } |
|
3276 |
|
3277 |
|
3278 void CAgnEntryModel::GetSortedAttachmentsL(RArray<TCalAttachmentUid>& aAttachmentIds, CCalAttachmentManager::TSortOrder aSortType) |
|
3279 { |
|
3280 RPointerArray<CAgnAttachmentIndexItem> sortedAttachments; |
|
3281 CleanupClosePushL(sortedAttachments); |
|
3282 iAttachmentIndex->GetSortedIndexL(aSortType, sortedAttachments); |
|
3283 |
|
3284 const TInt KAttachmentCount = sortedAttachments.Count(); |
|
3285 for ( TInt i = 0; i < KAttachmentCount; ++i) |
|
3286 { |
|
3287 aAttachmentIds.AppendL(sortedAttachments[i]->Uid()); |
|
3288 } |
|
3289 |
|
3290 CleanupStack::PopAndDestroy(&sortedAttachments); |
|
3291 } |
|
3292 |
|
3293 |
|
3294 CAgnAttachment* CAgnEntryModel::FetchAttachmentByIdL(TCalAttachmentUid aAttachUid) |
|
3295 { |
|
3296 _DBGLOG_ATTACH(AgmDebug::DebugLog("FetchAttachmentByIdL: Attachment Uid %d", aAttachUid);) |
|
3297 |
|
3298 const CAgnAttachmentIndexItem* const item = iAttachmentIndex->Attachment(aAttachUid); |
|
3299 CAgnAttachment* attachmentToWrite = NULL; |
|
3300 |
|
3301 if ( item ) |
|
3302 { |
|
3303 const RArray<TCalLocalUid>& KEntries = item->Entries(); |
|
3304 |
|
3305 if ( KEntries.Count() == 0 ) |
|
3306 { |
|
3307 // corrupt index - remove the attachment and return NULL |
|
3308 iAttachmentIndex->RemoveAttachment(aAttachUid); |
|
3309 } |
|
3310 else |
|
3311 { |
|
3312 const TCalLocalUid& Kid = KEntries[0]; |
|
3313 CAgnEntry* entry = FetchEntryL(Kid); |
|
3314 |
|
3315 if ( entry ) |
|
3316 { |
|
3317 CleanupStack::PushL(entry); |
|
3318 |
|
3319 for ( TInt i = 0; i < entry->AttachmentCount(); ++i ) |
|
3320 { |
|
3321 if ( entry->Attachment(i).Uid() == aAttachUid ) |
|
3322 { |
|
3323 attachmentToWrite = AttachmentFactory::CloneL(entry->Attachment(i)); |
|
3324 |
|
3325 break; |
|
3326 } |
|
3327 } |
|
3328 |
|
3329 CleanupStack::PopAndDestroy(entry); |
|
3330 } |
|
3331 } |
|
3332 } |
|
3333 |
|
3334 return attachmentToWrite; |
|
3335 } |
|
3336 |
|
3337 const TDesC8& CAgnEntryModel::GetEntryGuidL(CAgnEntry& aEntry) const |
|
3338 { |
|
3339 if(aEntry.Guid() == KNullDesC8) |
|
3340 {//it is a child and its uid has not been loaded |
|
3341 CAgnEntry* parentEntry = FetchEntryL(aEntry.ParentId()); |
|
3342 __ASSERT_DEBUG(parentEntry, Panic(EAgmErrChildWithoutParent)); |
|
3343 if(parentEntry) |
|
3344 { |
|
3345 CleanupStack::PushL(parentEntry); |
|
3346 HBufC8* guidHbuf = parentEntry->Guid().AllocL(); |
|
3347 CleanupStack::PopAndDestroy(parentEntry); |
|
3348 aEntry.SetGuid(guidHbuf); |
|
3349 } |
|
3350 } |
|
3351 return aEntry.Guid(); |
|
3352 } |
|
3353 |
|
3354 |
|
3355 /** |
|
3356 Returns A reference to the stream store in which the model's data is stored. |
|
3357 @internalComponent |
|
3358 */ |
|
3359 CStreamStore& CAgnEntryModel::StreamStore() const |
|
3360 { |
|
3361 return iEntryManager->StreamStore(); |
|
3362 } |
|
3363 |
|
3364 CCalAsyncDelete* CAgnEntryModel::CreateAsyncDeleteL(TAgnChangeFilter& aChangeFilter) |
|
3365 { |
|
3366 return CCalAsyncDelete::NewL(*this, aChangeFilter, *iSimpleEntryTable); |
|
3367 } |
|
3368 |
|
3369 void CAgnEntryModel::LoadNewStreamStoreL(CStreamStore& aStore, const TStreamId& aModelStreamId, CAgnEntryManager& aEntryManager, CAgnTzRuleIndex& aTzRuleIndex) |
|
3370 { |
|
3371 iEntryManager->SetStore(aStore); |
|
3372 |
|
3373 iEntryManager->CopyStreamIds(aEntryManager); |
|
3374 iModelStreamIdSet->LoadL(aStore, aModelStreamId); |
|
3375 |
|
3376 if(iTzRuleIndex) |
|
3377 { |
|
3378 delete iTzRuleIndex; |
|
3379 } |
|
3380 |
|
3381 iTzRuleIndex = &aTzRuleIndex; |
|
3382 iTzRuleIndex->CheckTzDbModificationL(*iAgnServerFile); |
|
3383 } |
|
3384 |
|
3385 TBool CAgnEntryModel::StreamsAreEmpty() const |
|
3386 { |
|
3387 return (iModelStreamIdSet->EntryStreamIdSet().Count() == 0); |
|
3388 } |
|
3389 |
|
3390 TAgnEntryIter* CAgnEntryModel::CreateEntryIterL() const |
|
3391 { |
|
3392 return new (ELeave) TAgnEntryIter(iModelStreamIdSet->EntryStreamIdSet(), *iEntryManager); |
|
3393 } |
|
3394 |
|
3395 CAgnCategoryIndex& CAgnEntryModel::CategoryIndex() const |
|
3396 /** Gets the category index. |
|
3397 |
|
3398 @return The agenda model category index object. */ |
|
3399 { |
|
3400 return ( *iCategoryIndex ); |
|
3401 } |
|
3402 |
|
3403 TTime CAgnEntryModel::TzRulesLastModifiedDateL() |
|
3404 { |
|
3405 return iTzRuleIndex->TzRulesLastModifiedDateL(); |
|
3406 } |
|
3407 |
|
3408 void CAgnEntryModel::HandleTzRulesChangeL(const TTime& aTime) |
|
3409 { |
|
3410 iTzRuleIndex->HandleTzRulesChangeL(aTime); |
|
3411 } |
|
3412 |
|
3413 CAgnAlarm& CAgnEntryModel::Alarm() |
|
3414 { |
|
3415 return *iAlarm; |
|
3416 } |