diff -r 000000000000 -r f979ecb2b13e pimappservices/calendar/server/src/agsentrymodel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pimappservices/calendar/server/src/agsentrymodel.cpp Tue Feb 02 10:12:19 2010 +0200 @@ -0,0 +1,3416 @@ +// Copyright (c) 1997-2009 Nokia Corporation and/or its subsidiary(-ies). +// All rights reserved. +// This component and the accompanying materials are made available +// under the terms of "Eclipse Public License v1.0" +// which accompanies this distribution, and is available +// at the URL "http://www.eclipse.org/legal/epl-v10.html". +// +// Initial Contributors: +// Nokia Corporation - initial contribution. +// +// Contributors: +// +// Description: +// + +#include "agsentrymodel.h" + +#include "agsalarm.h" +#include "agsasyncdelete.h" +#include "agsattachmentindex.h" +#include "agscategoryindex.h" +#include "agscategorylist.h" +#include "agsentrymanager.h" +#include "agmentry.h" +#include "agsextractor.h" +#include "agsfileconverter.h" +#include "agsiterator.h" +#include "agsstreamidset.h" +#include "agssort.h" +#include "agmutil.h" +#include "agmattendee.h" +#include "agmcategory.h" +#include "agssortinstance.h" +#include "agsindex.h" +#include "agmcontent.h" +#include "agsfilemanager.h" +#include "agsmain.h" +#include "agssess.h" +#include "agstzruleindex.h" +#include +#include "agsinstanceiterator.h" + +#include "agmdebug.h" + +#include +#include +#include + + +// Compact threshold dependent on the number of operations +const TInt KCompactOperationsThreshold = 32; + +/** This is called when opening an agenda file +@internalComponent + */ +CAgnEntryModel* CAgnEntryModel::NewL(CAgnServFile* aAgnServerFile) + { + CAgnEntryModel* self = new (ELeave) CAgnEntryModel(); + + CleanupStack::PushL(self); + self->ConstructL(aAgnServerFile); + CleanupStack::Pop(); + + return (self); + } + +/** Constructs a CAgnEntryModel object +@internalComponent + */ +void CAgnEntryModel::ConstructL(CAgnServFile* aAgnServerFile) + + { + iUpdateAlarm = ETrue; + iModelStreamIdSet = CAgnModelStreamIdSet::NewL(); + iEntryManager = CAgnEntryManager::NewL(); + iNextLocalUidValue = 1; + iNextAttachmentUid = 1; + + if ( aAgnServerFile ) + {//When opening a file + iAgnServerFile = aAgnServerFile; + iSimpleEntryTable = CAgnSimpleEntryTable::NewL(*this); + iExtractor = new (ELeave) TAgnInstanceExtractor(*iSimpleEntryTable); + iCategoryIndex = CAgnCategoryIndex::NewL(); + iAttachmentIndex = new (ELeave) CAgnAttachmentIndex; + CreateAlarmForServerL(); + } + iIndexFileIsDirty = ETrue; // for safety assume that the index + // file is out of date. We can correct this + // when we read the file + + iIndexFileIsPresent = ETrue; // Assume the index file is present. + // First attempt to read it will update this + // if it is not there. + iTzRuleIndex = NULL; + } + +/** +Frees all resources owned by the agenda model, prior to its destruction. + +@internalComponent +@capability None +*/ +CAgnEntryModel::~CAgnEntryModel() + { + ResetRollback(); + delete iCategoryIndex; + delete iAttachmentIndex; + delete iTzRuleIndex; + delete iExtractor; + delete iSimpleEntryTable; + delete iAlarm; + delete iModelStreamIdSet; + delete iEntryManager; + delete iCalConverter; + } + +const CAgnServFile& CAgnEntryModel::AgnServFile() + { + return *iAgnServerFile; + } + +/** Load up the stream network + */ +void CAgnEntryModel::DoOpenL(const TStreamId& aModelStreamId) + { + delete iCalConverter; + iCalConverter = NULL; + + // Check if a calendar converter is needed (old version file) + // + TAgnVersion fileVersion; + CalCommon::TCalFileVersionSupport status; + iAgnServerFile->GetFileVersionSupportStatusL(fileVersion,status); + + if ( status == CalCommon::EFileNeedsConverting ) + { + if ( iAgnServerFile->IsReadOnly() ) + { + User::Leave(KErrAccessDenied); + } + + iCalConverter = CalFileVersionUtils::CreateConverterL(fileVersion, *iAgnServerFile); + iModelStreamIdSet->LoadL(StreamStore(), aModelStreamId, *iCalConverter); + } + else + { + iTzRuleIndex = CAgnTzRuleIndex::NewL(*iAgnServerFile->Dictionary(), *iAgnServerFile->StoreL()); + iModelStreamIdSet->LoadL(StreamStore(), aModelStreamId); + //If the Calendar file needs to be converted, CheckTzDbModificationL should be called after the file conversion is completed (in method LoadNewStreamStoreL). + TAgnVersion curVersion = CalFileVersionUtils::CurrentFileVersion(); + if(status == CalCommon::EFileIsCurrentVersion && !(fileVersion == curVersion)) + { + //access to here means the format of index file has been changed but not to the rest + //Changing the index file format does not cause DC but we need to ignore the old index files + iIndexFileIsDirty = EFalse; + iIndexFileIsPresent = EFalse; + // Mark the existing index file as dirty and delete them. + // Trap the leave to keep things running, but there is nothing + // we can do if the file can't be deleted. + TRAP_IGNORE(MarkIndexFileAsDirtyL()); + + //Change the version of file to be current version. + TRAP_IGNORE(iModelStreamIdSet->ChangeFileVersionL(StreamStore(), aModelStreamId, curVersion)); + } + } + + GetFileIdL(); + InternalizeNextUidValuesL(); + InternalizeEntryManagerL(); + } + +void CAgnEntryModel::CheckTzDbModificationL() + { + if(iTzRuleIndex) + { + iTzRuleIndex->CheckTzDbModificationL(*iAgnServerFile); + } + } + +/** Opens an existing model store, whose root ID is aModelStreamId in the store aStore. + +@capability None +@param aStore The store in which the model's data is stored. +@param aId The root stream ID of the store aStore. + +@internalComponent +*/ +void CAgnEntryModel::OpenL(CStreamStore& aStore, const TStreamId& aModelStreamId) + { + Reset(); + + iEntryManager->SetStore(aStore); + DoOpenL(aModelStreamId); + } + +TStreamId CAgnEntryModel::CreateL(CStreamStore& aStore) + { + Reset(); + + iEntryManager->SetStore(aStore); + + TStreamId headStreamId = iModelStreamIdSet->CreateL(aStore, CalFileVersionUtils::CurrentFileVersion()); + + // save the next unique id value to the store + iNextLocalUidValue = 1; + iNextAttachmentUid = 1; + ExternalizeNextUidValuesL(); + + // save the entry store object to the store + ExternalizeEntryManagerL(); + + TTime creationDate; + creationDate.UniversalTime(); + iFileId = creationDate.Int64(); + TInt threeDidgit=Math::Random() % 1000; + iFileId = iFileId - threeDidgit;// Ensure the file ID is unique even when two files have been created at the same time. + ExternalizeFileIdL(aStore, iModelStreamIdSet->FileInformationStreamId()); + StreamStore().CommitL(); + + return (headStreamId); + } + +void CAgnEntryModel::ExternalizeFileIdL(CStreamStore& aStore, const TStreamId& aStreamId) const + { + RStoreWriteStream out; + // save file id + out.ReplaceLC(aStore, aStreamId); + out << iFileId; + out.CommitL(); + CleanupStack::PopAndDestroy(); //out + } + +/** +Adds aEntry to the store and returns its resulting entry id. +@capability None +*/ +TAgnEntryId CAgnEntryModel::DoAddEntryL(CAgnEntry& aEntry) + { + // If local UID is not set or if it's already used, then the entry is assigned a new local uid. + TBool useNextLocalUid = EFalse; + if (aEntry.LocalUid() == 0 || iSimpleEntryTable->GetEntry(aEntry.LocalUid()) != NULL) + { + useNextLocalUid = ETrue; + while (iSimpleEntryTable->GetEntry(++iNextLocalUidValue) != NULL) + { + } + aEntry.SetLocalUid(iNextLocalUidValue); + } + + TAgnEntryId nullId; + aEntry.SetEntryId(nullId); + + StoreExternalAttributesL(aEntry); + + CopyAttachmentFileToDifferentPlaceL(aEntry); + + //Add the tz rules in the entry to tz rule index before it is stored in entry manager. + if(iTzRuleIndex) + { + iTzRuleIndex->AddTzRuleL(aEntry); + } + + TStreamId newStreamId = iEntryManager->AddEntryL(aEntry); + if ( newStreamId != KNullStreamId ) + { + iModelStreamIdSet->EntryStreamIdSet().AddL(newStreamId); + } + + TAgnEntryId entryId = aEntry.EntryId(); + + if ( useNextLocalUid ) + { + ExternalizeNextUidValuesL(); + } + + iEntryManager->StoreBuffersL(); + ExternalizeEntryManagerL(); + + UpdateIndexL(aEntry, NULL, EAdd); + + // Don't commit on add. CommitL is called from CalInterimAPI after a number have been added. + + return (entryId); + } + +//It could copy the attachment to a different drive if it is the same as the existing one but with a different drive name +//or, it could copy the attachment form a different calendar file folder. +void CAgnEntryModel::CopyAttachmentFileToDifferentPlaceL(CAgnEntry& aEntry) + { + const TInt KAttachCount = aEntry.AttachmentCount(); + for (TInt ii = 0; ii < KAttachCount; ++ii) + { + CAgnAttachment& attach = aEntry.Attachment(ii); + if (attach.Uid() && attach.Type() == CCalContent::EDispositionInline) + { + CAgnAttachmentFile* attachFile = static_cast(&attach); + if(IsAttachmentFileFromSameSessionL(attachFile->FileName())) + { + const TDesC& existFileName = iAttachmentIndex->FileName(attach.Uid()); + if(existFileName != KNullDesC()) + {//There exists an attachment with same uid + TDriveName oldDrive = existFileName.Left(2); + TDriveName newDrive = attachFile->Drive(); + if(oldDrive.CompareF(newDrive) != 0)//drive is different + { + //Copy file to a new drive with the same name + HBufC* newfileName = existFileName.AllocLC(); + newfileName->Des().Replace(0,2,newDrive); + iAgnServerFile->CopyFileL(existFileName, newfileName->Des()); + attachFile->SetFileNameL(*newfileName); + CleanupStack::PopAndDestroy(newfileName); + } + } + } + else if (iAgnServerFile->FileExistsL(attachFile->FileName())) + { //Attachment has been copied from a different calendar session + TParsePtrC parseOriginalFile(attachFile->FileName()); + HBufC* fileName = GenerateFilenameLC(parseOriginalFile.Drive(), parseOriginalFile.NameAndExt()); + iAgnServerFile->CopyFileL(attachFile->FileName(), fileName->Des()); + attachFile->SetFileNameL(fileName->Des()); + CleanupStack::PopAndDestroy(fileName); + attachFile->SetUid(iNextAttachmentUid++); + } + } + } + } +//Find out if the attachment is copied from a different server session +TBool CAgnEntryModel::IsAttachmentFileFromSameSessionL(const TDesC& aAttachmentFile) + { + TBool ret = ETrue; + if(aAttachmentFile.FindF(iAgnServerFile->PrivatePath()) != KErrNotFound) + + {//The file name is something, for example, + //c:\private\private\10003a5c\calendar_filename_a\0\attachmentfilename + //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. + TInt lengthPath = iAgnServerFile->PrivatePath().Length(); + const TInt lengthDrive = 2; + //tempRemovedPath1 = calendar_filename_a\0\attachmentfilename + TPtrC tempRemovedPath1(aAttachmentFile.Mid(lengthPath + lengthDrive)); + //tempRemovedPath2 = calendar_filename_a + TPtrC tempRemovedPath2(tempRemovedPath1.Left(tempRemovedPath1.Locate('\\'))); + //calName = calendar_filename + TPtrC calName(tempRemovedPath2.Left(tempRemovedPath2.LocateReverse('_'))); + TParsePtrC parseFile(iAgnServerFile->FileName()); + if(calName.CompareF(parseFile.NameAndExt()) != 0) + { + ret = EFalse; + } + } + return ret; + } + +// aEntry is the entry that has been changed. If the change is an update, then aOriginalEntry gives +// details about the original entry. +void CAgnEntryModel::NotifyChangeL(const CAgnServerSession& aSession, CAgnEntry* aEntry, + MCalChangeCallBack2::TChangeType aChangeType, CAgnInstanceInfo* aOriginalEntry) + { + TAgnChange change; + change.iOperationType = aChangeType; + + if ( aEntry ) + { + change.iEntryType = aEntry->Type(); + change.iEntryId = aEntry->LocalUid(); + change.iRepeatRule = aEntry->RptDef(); + change.iStartTimeOfEntryUtc = aEntry->StartTime().UtcL(); + change.iEndTimeOfEntryUtc = aEntry->ValidToTimeLocalL(); + + if ( aOriginalEntry ) + { + change.iOriginalRepeatRule = aOriginalEntry->RptDef(); + change.iOriginalStartTimeUtc = aOriginalEntry->StartTimeUtc(); + change.iOriginalEndTimeUtc = aOriginalEntry->EndTimeUtc(); + } + else + { + change.iOriginalRepeatRule = NULL; + change.iOriginalStartTimeUtc = Time::NullTTime(); + change.iOriginalEndTimeUtc = Time::NullTTime(); + } + } + else + { + change.iEntryType = CCalEntry::EAppt; + change.iEntryId = 0; + change.iStartTimeOfEntryUtc = Time::NullTTime(); + change.iEndTimeOfEntryUtc = Time::NullTTime(); + change.iRepeatRule = NULL; + change.iOriginalStartTimeUtc = Time::NullTTime(); + change.iOriginalEndTimeUtc = Time::NullTTime(); + change.iOriginalRepeatRule = NULL; + } + + change.iSession = const_cast(&aSession); + GetFileIdL(); + change.iFileId = iFileId; + NotifySessionsOfChangeL(change); + } + +void CAgnEntryModel::NotifySessionsOfChangeL(const TAgnChange& aChange) + { + RPointerArray sessions; + CleanupClosePushL(sessions); + iAgnServerFile->Server().FetchSessionsL(sessions); + const TInt count = sessions.Count(); + for ( TInt i = 0; i < count; ++i ) + { + sessions[i]->AddChangeL(aChange); + } + CleanupStack::PopAndDestroy(&sessions); // sessions.Close() + } + +void CAgnEntryModel::NotifyPublishAndSubscribeL(TAgnChangeFilter& aChangeFilter) + { + // if publish and subcribe is enabled... + if ( aChangeFilter.PubSubEnabled() ) + { + // ...get and set the Publish and Subscribe data + TCalPubSubData calPubSubData; + calPubSubData.iFileNameHash = iAgnServerFile->FileNameHash(); + calPubSubData.iTimeOfChangeUtc.UniversalTime(); + + TPckgBuf calBuf(calPubSubData); + + // publish the update + if ( aChangeFilter.TodoChanged() ) + { + User::LeaveIfError(RProperty::Set(KCalPubSubCategory, ECalPubSubTodoNotification, calBuf)); + } + else + { + User::LeaveIfError(RProperty::Set(KCalPubSubCategory, ECalPubSubEventNotification, calBuf)); + } + + aChangeFilter.SetPubSubChange(TAgnChangeFilter::ENoChange); + } + } + +/** +Add a entry to the file. This function decides whether a new entry should be added or if an existing entry +should be replaced. It also handles the adding of child entries. +@internalComponent +@return the TAgnEntryId of the newly added entry. +@param aEntry The entry being stored. +@param aChangeFilter Specifies the notification filter for the session adding the entry. +@leave KErrArgument If aEntry is a parent but contains a recurrence Id. +@leave KErrNotFound If aEntry is a child entry whose parent entry doesn't exist in the calendar store. +@leave KErrArgument If aEntry is a child entry with a different time mode (fixed / floating) to its parent entry. +*/ +TAgnEntryId CAgnEntryModel::StoreL(CAgnEntry& aEntry, TAgnChangeFilter* aChangeFilter) + { + iChangeFilter = aChangeFilter; + TAgnEntryId returnId; + + if ( aEntry.GsDataType() == CGsData::EParent ) + { + _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Entry to be stored is a parent");) + _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Checking if an entry with the same GUID already exists");) + + // it is a parent entry + CAgnEntry* existingEntryToReplace = FetchEntryL(aEntry.Guid()); + + if (existingEntryToReplace == NULL && aEntry.LocalUid() != 0) + { + //This is used for the case when sync a updated entry from remote sync server. + //Some servers may filter out GUID assigned by client and only info we can get + //to update a entry is by using its local ID. + _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Attempting to fetch using localUid to fetch the entry from the server");) + existingEntryToReplace = FetchEntryL(aEntry.LocalUid()); + } + + if ( existingEntryToReplace) + { + _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: An entry with the same GUID already exists");) + + CleanupStack::PushL(existingEntryToReplace); + if (existingEntryToReplace->Type() == aEntry.Type()) + { + _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Existing entry type matches incoming entry's type");) + + // if this entry is the same type as the existing entry, update the existing entry + aEntry.SetEntryId(existingEntryToReplace->EntryId()); + aEntry.SetLocalUid(existingEntryToReplace->LocalUid()); + + _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Setting Ids using the existing entry: LocalUid= %d, EntryId= %d", aEntry.LocalUid(),aEntry.EntryId().Value());) + + const RArray& KChildIds = existingEntryToReplace->ChildIds(); + const TInt KCount = KChildIds.Count(); + for ( TInt ii = 0; ii < KCount; ++ii ) + { + aEntry.AddChildIdL(KChildIds[ii]); + } + + aEntry.SetLastModifiedDate(); + + UpdateEntryL(aEntry, iChangeFilter, ETrue); + + returnId = aEntry.EntryId(); + } + else + { + _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Existing entry types is different to incoming entry's type");) + _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Delete the existing entry and add the incoming entry as a new one");) + + // if the entry is a different type, delete the old entry and add the new one + DeleteEntryL(*existingEntryToReplace, ETrue, iChangeFilter); + returnId = AddEntryL(aEntry); + } + + CleanupStack::PopAndDestroy(existingEntryToReplace); + } + else + { + _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Matching existing entry not found. Adding a new entry");) + returnId = AddEntryL(aEntry); + } + } + else + { + _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Entry to be stored is a child");) + //if this is a child entry, fetch the parent from the child's GUID (if present) or Local UID + CAgnEntry* parentEntry = NULL; + if ( aEntry.Guid() != KNullDesC8 ) + { + parentEntry = FetchEntryL(aEntry.Guid()); + } + else + { + parentEntry = FetchEntryL(aEntry.ParentId()); + } + + if(parentEntry==NULL) + { + #if defined (__CAL_BASIC_LOGGING__) || (__CAL_ENTRY_LOGGING__) || (__CAL_VERBOSE_LOGGING) + AgmDebug::DebugLog("StoreL: KErrNotFound: Parent entry with the GUID or localUid doesnt exist"); + #endif + + User::Leave(KErrNotFound); + } + + CleanupStack::PushL(parentEntry); + + if ( parentEntry->TimeMode() != aEntry.TimeMode() || parentEntry->Type() != aEntry.Type() ) + { + #if defined (__CAL_BASIC_LOGGING__) || (__CAL_ENTRY_LOGGING__) || (__CAL_VERBOSE_LOGGING__) + AgmDebug::DebugLog("StoreL: KErrArgument: Child entry with different time mode or type from the parent entry is not allowed"); + #endif + + // don't allow a child entry to be different time mode or type from the parent entry + User::Leave(KErrArgument); + } + + if ( ! aEntry.RecurrenceId().IsSet()) + { + _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Recurrence ID not set on the incoming entry. Setting it using the parent");) + aEntry.SetRecurrenceIdFromParentL(*parentEntry); + } + + // We allow only repeating parents to have children that attempt to change a parent + // schedule.(If the user's intention is to modify a non-repeating parent, the entire + // parent should be replaced with a new entry). + const CAgnRptDef* KParentRptDef = parentEntry->RptDef(); + + if(KParentRptDef==NULL) + { + _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Parent is not repeating. Only repeating parents are allowed to have children");) + _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");) + + User::Leave(KErrArgument); + } + + if ( aEntry.RptDef() ) + { + _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Incoming entry is a repeating child entry");) + returnId = AddRepeatingChildEntryUpdateParentRuleL(*parentEntry, aEntry); + } + else + { + _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Incoming entry is a non-repeating child entry");) + returnId = AddNonRepeatingChildEntryUpdateParentExceptionsL(*parentEntry, aEntry); + } + + CleanupStack::PopAndDestroy(parentEntry); + } + _DBGLOG_VERBOSE(AgmDebug::DebugLog("StoreL: Entry added successfully");) + _DBGLOG_ENTRY(AgmDebug::DebugLogEntryL(aEntry, EDumpEntryAll);) + + return (returnId); + } + +TStreamId CAgnEntryModel::WriteDescriptorToStreamL(const TDesC& aString) + { + if (aString.Length() > 0) + { + RStoreWriteStream out; + TStreamId id = out.CreateLC(StreamStore()); + out.WriteUint32L(aString.Length()); + out << aString; + out.CommitL(); + CleanupStack::PopAndDestroy(); //out + return id; + } + return KNullStreamId; + } + +/** +Store those entry properties which are stored in a separate stream: summary, description, alarm action. +@capability WriteUserData +*/ +void CAgnEntryModel::StoreExternalAttributesL(CAgnEntry& aEntry) + { + TStreamId id = WriteDescriptorToStreamL(aEntry.Description()); + aEntry.SetDescriptionStreamId(id); + + id = WriteDescriptorToStreamL(aEntry.Summary()); + aEntry.SetSummaryStreamId(id); + + if ( aEntry.AlarmAction() ) + { + RStoreWriteStream out; + id = out.CreateLC(StreamStore()); + out << *aEntry.AlarmAction(); + out.CommitL(); + CleanupStack::PopAndDestroy(); //out + aEntry.SetAlarmActionStreamId(id); + } + } + +void CAgnEntryModel::DeleteExternalAttributesL(CAgnEntry& aEntry) + { + if ( aEntry.DescriptionStreamId() != KNullStreamId ) + { + StreamStore().DeleteL(aEntry.DescriptionStreamId()); + aEntry.SetDescriptionStreamId(KNullStreamId); + } + + if ( aEntry.SummaryStreamId() != KNullStreamId ) + { + StreamStore().DeleteL(aEntry.SummaryStreamId()); + aEntry.SetSummaryStreamId(KNullStreamId); + } + + if ( aEntry.AlarmActionStreamId() != KNullStreamId ) + { + StreamStore().DeleteL(aEntry.AlarmActionStreamId()); + aEntry.SetAlarmActionStreamId(KNullStreamId); + } + } + +void CAgnEntryModel::UpdateExternalAttributesL(CAgnEntry& aEntry) + { + // 3 scenarios where we do something for updating notes text + TStreamId id = KNullStreamId; + + switch ( aEntry.DescriptionChange() ) + { + case CAgnEntry::EAgnDataDeleted: + DeleteTextStreamL(aEntry.DescriptionStreamId()); + aEntry.SetDescriptionStreamId(KNullStreamId); + break; + + case CAgnEntry::EAgnDataAdded: + id = StoreTextL(aEntry.Description()); + aEntry.SetDescriptionStreamId(id); + break; + + case CAgnEntry::EAgnDataUpdated: + UpdateTextL(aEntry.Description(), aEntry.DescriptionStreamId()); + break; + + default://no changes + break; + } + + // 3 scenarios where we do something for updating summary text + switch ( aEntry.SummaryChange() ) + { + case CAgnEntry::EAgnDataDeleted: + DeleteTextStreamL(aEntry.SummaryStreamId()); + aEntry.SetSummaryStreamId(KNullStreamId); + break; + + case CAgnEntry::EAgnDataAdded: + id = StoreTextL(aEntry.Summary()); + aEntry.SetSummaryStreamId(id); + break; + + case CAgnEntry::EAgnDataUpdated: + UpdateTextL(aEntry.Summary(), aEntry.SummaryStreamId()); + break; + + default://no changes + break; + } + + // 3 scenarios where we do something for updating alarm action + switch ( aEntry.AlarmActionChange() ) + { + case CAgnEntry::EAgnDataDeleted: + DeleteAlarmActionStreamL(aEntry.AlarmActionStreamId()); + aEntry.SetAlarmActionStreamId(KNullStreamId); + break; + + case CAgnEntry::EAgnDataAdded: + id = StoreAlarmActionL(*(aEntry.AlarmAction())); + aEntry.SetAlarmActionStreamId(id); + break; + + case CAgnEntry::EAgnDataUpdated: + UpdateAlarmActionL(*(aEntry.AlarmAction()), aEntry.AlarmActionStreamId()); + break; + + default://no changes + break; + } + } + + +TAgnEntryId CAgnEntryModel::AddEntryL(CAgnEntry& aEntry) + { + TAgnEntryId id; + + id = DoAddEntryL(aEntry); + + NotifyingL(MCalChangeCallBack2::EChangeAdd, aEntry, NULL); + + if (iUpdateAlarm && aEntry.HasAlarm()) + { + iAlarm->FindAndQueueNextAlarmL(EFalse); + } + + return (id); + } + + +// Update all the indexes which are in RAM after an entry operation (add, update, delete) +void CAgnEntryModel::UpdateIndexL(CAgnEntry& aEntry, CAgnEntry* aOldEntry, TUpdateIndex aUpdateIndex) + { + // set guid hash before adding / updating an entry in the GUID hash index + if ( aEntry.GsDataType() == CGsData::EParent && ! aEntry.GuidHash() ) + { + aEntry.SetGuidHash( GenerateHash8L(aEntry.Guid()) ); + if ( aOldEntry ) + { + aOldEntry->SetGuidHash( aEntry.GuidHash() ); + } + } + + switch( aUpdateIndex ) + { + case EAdd: + { + AppendRollbackArrayL(aEntry, ETrue); + + AddEntryToIndexesL(aEntry); + } + break; + + case EDelete: + { + AppendRollbackArrayL(aEntry, EFalse); + + iSimpleEntryTable->DeleteEntry(aEntry.EntryId()); + + iCategoryIndex->DeleteEntryL(aEntry.EntryId()); + iAttachmentIndex->DeleteEntryL(aEntry); + } + break; + + case EUpdate: + { + __ASSERT_ALWAYS(aOldEntry, Panic(EAgmErrUpdateInvalid)); + AppendRollbackArrayL(*aOldEntry, EFalse); + iSimpleEntryTable->DeleteEntry(aOldEntry->EntryId()); + + AppendRollbackArrayL(aEntry, ETrue); + + AddEntryToIndexesL(aEntry); + } + break; + + case EBuildIndex: + { + AppendRollbackArrayL(aEntry, ETrue); + + if(iTzRuleIndex) + { + iTzRuleIndex->FetchTzRuleL(aEntry); + } + AddEntryToIndexesL(aEntry); + } + break; + + default: + Panic(EAgmErrInvalidIndexUpdate); + break; + } + // Increment the operations counter which triggers compacting upon reaching a threshold + ++iOperationsCounter; + } + +// Add an entry to all indexes in RAM +void CAgnEntryModel::AddEntryToIndexesL(CAgnEntry& aEntry) + { + aEntry.SetCollectionId(iAgnServerFile->CollectionId()); + iSimpleEntryTable->AddEntryL(aEntry); + + iAgnServerFile->CategoryList().AddEntryL(aEntry); + iCategoryIndex->UpdateEntryL(aEntry.EntryId(), aEntry); + iAttachmentIndex->AddEntryL(aEntry); + } + +/** +Returns true if the entry is found to have any children which are repeating. +@internalComponent +@return TBool. +@param aEntry The entry. +*/ +TBool CAgnEntryModel::EntryHasRepeatingChildrenL(const CAgnEntry& aParentEntry) + { + const RArray& KChildIds = aParentEntry.ChildIds(); + + const TInt KIdCount = KChildIds.Count(); + + TBool repeatingChildExists = EFalse; + + for ( TInt pos = 0; ! repeatingChildExists && pos < KIdCount; ++pos ) + { + CAgnEntry* childOfParentEntry = FetchEntryL(KChildIds[pos].ChildId()); + if (childOfParentEntry) + { + repeatingChildExists = (childOfParentEntry && childOfParentEntry->RptDef()); + + delete childOfParentEntry; + } + } + + return (repeatingChildExists); + } + +/** +Deletes any non-repeating children within a specified range. This is called when adding a repeating child entry to +delete existing non-repeating child entries - those that appear either before or after the repeating child entry's recurrence ID, +depending on the recurrence range. +@internalComponent +@param aParentEntry The parent entry +@param aRecId Indicates the recurrence ID of the repeating child entry +@param aRange Indicates "this and prior" or "this and future" to delete child entries on one side of the recurrence ID. +*/ +void CAgnEntryModel::DeleteNonRepeatingChildrenOutsideRangeL(const CAgnEntry& aParentEntry, const TAgnCalendarTime& aRecId, CalCommon::TRecurrenceRange aRange) + { + const RArray& KChildIds = aParentEntry.ChildIds(); + + const TInt KCount(KChildIds.Count()); + + for ( TInt i = 0; i < KCount; ++i ) + { + // Check if any child falls outside parent's rpt rule + if ( (aRecId > KChildIds[i].RecurrenceId() && aRange == CalCommon::EThisAndPrior) || + (aRecId < KChildIds[i].RecurrenceId() && aRange == CalCommon::EThisAndFuture) ) + { + // Remove child if it is non-repeating + TCalLocalUid chIdRemove(KChildIds[i].ChildId()); + CAgnEntry* chIdEntry = FetchEntryL(chIdRemove); + if (chIdEntry) + { + CleanupStack::PushL(chIdEntry); + + __ASSERT_ALWAYS( ! chIdEntry->RptDef(), Panic(EAgmErrAddingSecondRepeatingChildEntry)); + DeleteEntryL(*chIdEntry, ETrue, iChangeFilter); + + CleanupStack::PopAndDestroy(chIdEntry); + } + } + } + } + +/** +Adds a repeating child to a repeating parent, and performs the necessary adjustments to the parent's repeat rule. +The explanation of how the parent's rpt rule is trimmed is given in the code comments. +@internalComponent +@param aParentEntry The parent entry +@param aRepeatingChild The child to be added +@leave KErrArgument If either aParentEntry or aRepeatingChild are non-repeating +@leave KErrArgument If aRepeatingChild's RecurrenceId is aParentEntry's first or last instance +@leave KErrNotSupported If aRepeatingChild's Range is ECurrentInstance +@leave KErrArgument If aRepeatingChild's Range is an unexpected value +*/ +TAgnEntryId CAgnEntryModel::AddRepeatingChildEntryUpdateParentRuleL(CAgnEntry& aParentEntry, CAgnEntry& aRepeatingChild) + { + // Ensure both parent and child are repeating + __ASSERT_ALWAYS((aRepeatingChild.RptDef() && aParentEntry.RptDef()), User::Leave(KErrArgument)); + TAgnCalendarTime childRecId = aRepeatingChild.RecurrenceId(); + + TAgnEntryId retId; + + CAgnRptDef* parentRptDef = aParentEntry.RptDef(); + + TTime recIdLocal(childRecId.LocalL()); + TDateTime recIdLocalDateTime(recIdLocal.DateTime()); + + CalCommon::TRecurrenceRange range = aRepeatingChild.RecurrenceRange(); + + if(range == CalCommon::EThisOnly) + { + // if the range specified is EThisOnly, attempt to recalculate the recurrence range + // so that the maximum number of "overlapping" entries (schedules running concurrently in the + // parent and child) is removed + TTime dtStartLocal = aParentEntry.StartTime().LocalL(); + TDateTime dtStartLocalDateTime =(dtStartLocal.DateTime()); + if (dtStartLocalDateTime.Year() == recIdLocalDateTime.Year() && + dtStartLocalDateTime.Month() == recIdLocalDateTime.Month() && + dtStartLocalDateTime.Day() == recIdLocalDateTime.Day()) + { + // Check whether the recurrence ID of the child is of the same day as the + // first instance of the parent. If so, assume range is ThisAndPrior + // to avoid the parent repeating rule being completely trimmed. + // + // Note: It is possible that this is a previously trimmed parent, as a result of + // the trimming, completely embedded by the child. Therefore, no rejection of + // such child/parent range is performed to ensure an Import->Export->Import will + // not be failed. In the rare occurrence where the child entry completely embeds + // the parent entry and has RecID on the same day as start/end of parent entry, it will + // be allowed through. + range = CalCommon::EThisAndPrior; + } + else + { + TTime dtUntilLocal = parentRptDef->LastInstanceL().LocalL(); + + TDateTime dtUntilLocalDateTime =(dtUntilLocal.DateTime()); + if (dtUntilLocalDateTime.Year() == recIdLocalDateTime.Year() && + dtUntilLocalDateTime.Month() == recIdLocalDateTime.Month() && + dtUntilLocalDateTime.Day() == recIdLocalDateTime.Day()) + { + // Check whether the recurrence ID of the child is of the same day as the + // last instance of the parent. If so, assume range is ThisAndFuture + // instances to avoid the parent repeating rule being completely trimmed. + // + // Note: It is possible that this is a previously trimmed parent, as a result of + // the trimming, completely embedded by the child. Therefore, no rejection of + // such child/parent range is performed to ensure an Import->Export->Import will + // not be failed. In the rare occurrence where the child entry completely + // embeds the parent entry and has RecID on the same day as start/end of parent + // entry, it will be allowed through. + range = CalCommon::EThisAndFuture; + } + else + { + // Reject the child entry if it completely alters the parent entry. This should be done by + // replacing the parent schedule instead. + // + // An exception is when the parent entry's start/end date is the same as the RecID. That + // could be the result of the parent being previously trimmed. + __ASSERT_ALWAYS(parentRptDef->LastInstanceL() > aRepeatingChild.RptDef()->LastInstanceL() || + aRepeatingChild.RptDef()->FirstInstanceL() > aParentEntry.StartTime(), + User::Leave(KErrArgument)); + // if the RecId is neither at the start/end of the parent's rpt range, + // then base on the distance of the beginning & end of the child recurreance range + // from the RecId, determine if the range should be assumed as ThisAndFuture + // or a ThisAndPrior scenario. + // + // As this usage is most likely ThisAndFuture, so even if the distance from + // beginning & end of the child repeat range is the same, assume CurrentAndFuture. + TTimeIntervalDays afterRecurrence = (aRepeatingChild.RptDef()->LastInstanceL().UtcL()).DaysFrom(childRecId.UtcL()); + TTimeIntervalDays beforeRecurrence = childRecId.UtcL().DaysFrom(aRepeatingChild.RptDef()->FirstInstanceL().UtcL()); + range = (afterRecurrence >= beforeRecurrence) ? + CalCommon::EThisAndFuture : CalCommon::EThisAndPrior; + } + } + aRepeatingChild.SetRecurrenceRangeL(range); + } + + // Trim parent's repeat rule to fall in line with the new child entry, + // and deal with any exceptions and sporadic dates that fall within the + // affected period. + + switch (range) + { + case CalCommon::EThisAndFuture: + { + // Reject if RecurrenceId falls on 1st instance of parent's (original) rpt rule + // If the User's intention is to modify the entire parent schedule, they should replace the + // existing parent with a new parent entry (by submitting an entry with no RecId specified). + TTime firstInstanceParentUtc(Time::NullTTime()); + parentRptDef->NudgeNextInstanceUtcL(aParentEntry.StartTime().UtcL(), firstInstanceParentUtc); + + // Ensure child's RecurrenceId is not the parent's first instance. + __ASSERT_ALWAYS(childRecId.UtcL() > firstInstanceParentUtc, User::Leave(KErrArgument)); + + // Store child entry + retId = AddChildEntryL(aRepeatingChild, aParentEntry); + childRecId = aRepeatingChild.RecurrenceId(); // recurrence ID may have been updated if it was an imported rec ID with no time + + // Set parent's rpt-rule end date to child's RecId (already verified RecId points to a genuine instance on the parent). + if (parentRptDef->RRule()) + { + parentRptDef->SetUntilTime(childRecId); + } + } + break; + + case CalCommon::EThisAndPrior: + { + // Reject if RecurrenceId falls on last instance of parent's (original) rpt rule + // If the User's intention is to modify the entire parent schedule, they should replace the + // existing parent with a new parent entry (by submitting an entry with no RecId specified). + TTime lastInstanceParentUtc(Time::NullTTime()); + parentRptDef->NudgePreviousInstanceUtcL(parentRptDef->LastInstanceL().UtcL(), lastInstanceParentUtc); + + // Ensure child's RecurrenceId is not the parent's last instance. + __ASSERT_ALWAYS(childRecId.UtcL() < lastInstanceParentUtc, User::Leave(KErrArgument)); + + // Store child entry + retId = AddChildEntryL(aRepeatingChild, aParentEntry); + childRecId = aRepeatingChild.RecurrenceId(); // recurrence ID may have been updated if it was an imported rec ID with no time + + // Move parent's start time to child's RecId (already verified RecId points to a genuine instance on the parent). + aParentEntry.MoveStartTimeLocalL(childRecId.LocalL()); + } + break; + + default: + { + User::Leave(KErrArgument); + } + break; + } + + // add an exception to the parent on the child's recurrence ID + aParentEntry.RptDef()->AddExceptionL(childRecId); + + // Remove any exceptions on the parent that fall in the discarded range + aParentEntry.RptDef()->PruneExceptionsL(); + + // Remove any sporadic dates on the parent that fall in the discarded range + aParentEntry.PruneRDatesL(childRecId, range); + + // Commit parent to store + aParentEntry.SetLastModifiedDate(); + UpdateEntryL(aParentEntry, iChangeFilter, EFalse); + + // Delete any non-repeating children that fall in the discarded range + DeleteNonRepeatingChildrenOutsideRangeL(aParentEntry, childRecId, range); + + return (retId); + } + + +/** +Adds a non-repeating child to a repeating parent, and then adds an exception to the parent's exception +list for the occurrence given by the child's RecId. +@internalComponent +@param aParentEntry The parent entry +@param aNonRepeatingChild The child to be added +@leave KErrArgument If parent entry does not have a repeat definition +*/ +TAgnEntryId CAgnEntryModel::AddNonRepeatingChildEntryUpdateParentExceptionsL(CAgnEntry& aParentEntry, CAgnEntry& aNonRepeatingChild) + { + const TInt KNumchildrenBefore = aParentEntry.ChildIds().Count(); + // Add\Update children entry + TAgnEntryId retId = AddChildEntryL(aNonRepeatingChild, aParentEntry); + const TInt KNumchildrenAfter = aParentEntry.ChildIds().Count(); + + TBool addexception = !aParentEntry.RptDef()->FindException(aNonRepeatingChild.RecurrenceId()); + if ( KNumchildrenAfter > KNumchildrenBefore || addexception ) + { + //only add exception and update parent if a child has been added otherwise a existing child has been updated. + aParentEntry.RptDef()->AddExceptionL(aNonRepeatingChild.RecurrenceId()); + aParentEntry.SetLastModifiedDate(); + UpdateEntryL(aParentEntry, iChangeFilter, EFalse); + } + + return (retId); + } + + +TAgnEntryId CAgnEntryModel::AddChildEntryL(CAgnEntry& aChild, CAgnEntry& aParent) + { + TAgnCalendarTime entryRecId = aChild.RecurrenceId(); + CAgnRptDef* parentRptDef = aParent.RptDef(); + __ASSERT_ALWAYS(parentRptDef, Panic(EAgmErrAddingChildEntryToNonRepeatingParent)); + + // Microsoft export recurrence id with date but no time. Test if current + // recurrenceId is an instance of parent entry so that when the time is + // not specified in recurrenceId we can find the right occurence from the parent. + if ( ! parentRptDef->IsAnInstanceL(entryRecId.LocalL()) ) + { + // If a midnight recurrence Id is not floating, and is not an instance of the parent entry, then + // it means it is imported without the time and timezone information. Therefore, it has to be + // converted to a correct UTC time here before nudging the instance. + TDateTime recIdDateTime = entryRecId.UtcL().DateTime(); + if (recIdDateTime.Hour() == 0 && recIdDateTime.Minute() == 0 && + aChild.TimeMode() != MAgnCalendarTimeMode::EFloating) + { + entryRecId.SetUtcL(parentRptDef->ConvertFromRepeatLocalToUtcL(entryRecId.UtcL())); + } + + TTime actualInstanceTimeUtc; + parentRptDef->NudgeNextInstanceUtcL(entryRecId.UtcL(), actualInstanceTimeUtc); + // If recurrenceId is not an instance of the parent then nudge to next occurence if an instance can be found for the same date. + if ( actualInstanceTimeUtc != Time::NullTTime() ) + { + const TTime KActualInstanceTimeRptLocal = parentRptDef->ConvertFromUtcToRepeatLocalL(actualInstanceTimeUtc); + const TTime KRecurrenceIdRptLocal = parentRptDef->ConvertFromUtcToRepeatLocalL(entryRecId.UtcL()); + + const TDateTime KActualInstanceDateTimeRptLocal = KActualInstanceTimeRptLocal.DateTime(); + const TDateTime KRecurrenceIdDateTimeRptLocal = KRecurrenceIdRptLocal.DateTime(); + + // Only change recurrenceId if nudged date is the same as the recurrenceId date + if ( KRecurrenceIdDateTimeRptLocal.Year() == KActualInstanceDateTimeRptLocal.Year() && + KRecurrenceIdDateTimeRptLocal.Month() == KActualInstanceDateTimeRptLocal.Month() && + KRecurrenceIdDateTimeRptLocal.Day() == KActualInstanceDateTimeRptLocal.Day() ) + { + if (aParent.TimeMode() == MAgnCalendarTimeMode::EFloating) + { + entryRecId.SetFloatingL(KActualInstanceTimeRptLocal); + } + else + { + entryRecId.SetUtcL(actualInstanceTimeUtc); + } + aChild.UpdateRecurrenceIdL(entryRecId); + } + else + { + User::Leave(KErrNotFound); // If leave occurs here then there is no event found for given day by recurrenceId + } + } + else + { + User::Leave(KErrNotFound); // leave occurs here because the next instance cannot be found + } + } + + aChild.SetParentId(aParent.LocalUid()); +// First check if the same child entry already exists in the store (one with the same RecId). + // If so we delete it and replace it with the new child. + // This allows the client to 'update' a child entry without having to destroy the entire + // associated set. + + CAgnEntry* existingChildEntry = FetchEntryL(aParent.Guid(), entryRecId); + + TAgnEntryId retId; + TBool addChild(ETrue); + + if ( existingChildEntry ) + { + CleanupStack::PushL(existingChildEntry); + if ( existingChildEntry->Type() == aChild.Type() ) + {// Update existing child entry + aChild.SetLocalUid(existingChildEntry->LocalUid()); + aChild.SetEntryId(existingChildEntry->EntryId()); + UpdateEntryL(aChild, iChangeFilter, EFalse); + retId = aChild.EntryId(); + addChild = EFalse; + } + else + { + // Delete existing child entry + aChild.SetLocalUid(existingChildEntry->LocalUid()); + DeleteEntryL(*existingChildEntry, EFalse, iChangeFilter); + } + CleanupStack::PopAndDestroy(existingChildEntry); + } + + if ( addChild ) + { + if ( aChild.RptDef() ) + { + // Check there are no other repeating childs on this parent. + // (Limit to only ONE rpt rule change to a parent entry) + __ASSERT_ALWAYS(!EntryHasRepeatingChildrenL(aParent), User::Leave(KErrNotSupported)); + } + + // add the new child entry + retId = AddEntryL(aChild); + + // Add the occurrence given by the RecId to the parent's Exception List. + TGsChildRefData child(aChild.LocalUid(), entryRecId); + aParent.AddChildIdL(child); + } + + return (retId); + } + + +CAgnEntry* CAgnEntryModel::FetchEntryL(const TDesC8& aGuid) const + { + #ifdef __CAL_VERBOSE_LOGGING__ + { + TBuf guidBuf; + guidBuf.Copy(aGuid); + AgmDebug::DebugLog("FetchEntryL: Fetching entry with GUID='%S'",&guidBuf); + } + #endif + + RArray candidateMatches; + CleanupClosePushL(candidateMatches); + + iSimpleEntryTable->FindByHashL(GenerateHash8L(aGuid), candidateMatches); + + CAgnEntry* entry = NULL; + const TInt KCount = candidateMatches.Count(); + + for ( TInt i = 0; i < KCount; ++i ) + { + CAgnEntry* candidateEntry = FetchEntryL(candidateMatches[i]); + __ASSERT_ALWAYS(candidateEntry, User::Leave(KErrNotFound)); // entry table contains an entry not in the store + + CleanupStack::PushL(candidateEntry); + + if ( candidateEntry->Guid() == aGuid ) + { + entry = candidateEntry; + CleanupStack::Pop(candidateEntry); + break; + } + + CleanupStack::PopAndDestroy(candidateEntry); + } + + CleanupStack::PopAndDestroy(); //candidateMatches.Close(); + + return (entry); + } + + +CAgnEntry* CAgnEntryModel::FindChildFromParentL(const CAgnEntry& aParent, const TAgnCalendarTime& aRecurrenceId) const + { + CAgnEntry* returnEntry = NULL; + + // Get Child ids + const RArray& KChildIds = aParent.ChildIds(); + + // Check if we have a match amongst the children of this parent + const TInt KCount = KChildIds.Count(); + + for ( TInt pos = 0; pos < KCount; ++pos ) + { + const TGsChildRefData& KChildData = KChildIds[pos]; + + if ( KChildData.RecurrenceId() == aRecurrenceId ) + { + returnEntry = FetchEntryL(KChildData.ChildId()); + + break; + } + } + + return (returnEntry); + } + +/** Save the contents of iNextLocalUidValue and iNextAttachmentUid to the store +@internalComponent +*/ +void CAgnEntryModel::ExternalizeNextUidValuesL() const + { + ExternalizeNextUidValuesL(StreamStore(), iModelStreamIdSet->NextLocalUidValueStreamId()); + } + +void CAgnEntryModel::ExternalizeNextUidValuesL(CStreamStore& aStreamStore, const TStreamId& aStreamId) const + { + RStoreWriteStream stream; + + stream.ReplaceLC(aStreamStore, aStreamId); + stream.WriteUint32L(iNextLocalUidValue); + stream.WriteUint32L(iNextAttachmentUid); + + stream.CommitL(); + + CleanupStack::PopAndDestroy(); + } + +void CAgnEntryModel::InternalizeNextUidValuesL() + { + RStoreReadStream in; + in.OpenLC(StreamStore(), iModelStreamIdSet->NextLocalUidValueStreamId()); + if (iCalConverter) + { + iCalConverter->InternalizeNextUidValuesL(in); + } + else + { + iNextLocalUidValue = in.ReadUint32L(); + iNextAttachmentUid = in.ReadUint32L(); + } + + CleanupStack::PopAndDestroy(&in); + } + +/** Sets whether or not to use buffered deletion. + +Buffered deletion means that when entries are deleted they are only +marked as being deleted in the internal memory buffer and the file +is not updated until either every entry in the buffer is marked as being +deleted or a commit/flush is called. +@param aSetting ETrue for buffered deletion, EFalse for non-buffered deletion. +@capability None +*/ +void CAgnEntryModel::SetBufferedDeleting(TBool aSetting) + { + iEntryManager->SetBufferedDeleting(aSetting); + } + +/** +Flush out the entry store +*/ +void CAgnEntryModel::FlushL() + { + iEntryManager->FlushBuffersL(); + } + +/** Resets any file specific data before opening a new calendar file. +@internalAll +*/ +void CAgnEntryModel::Reset() + { + iFileId = 0; + + if (iModelStreamIdSet) + { + iModelStreamIdSet->Reset(); + } + + if (iEntryManager) + { + iEntryManager->Reset(); + } + + if (iSimpleEntryTable) + { + iSimpleEntryTable->Reset(); + } + } + +/** Save the entry manager and its data to the store +@internalAll +*/ +void CAgnEntryModel::ExternalizeEntryManagerL() const + { + RStoreWriteStream stream; + stream.ReplaceLC(StreamStore(),iModelStreamIdSet->EntryManagerStreamId()); + stream << *iEntryManager; + stream.CommitL(); + CleanupStack::PopAndDestroy(); + } + +void CAgnEntryModel::InternalizeEntryManagerL() + { + RStoreReadStream in; + in.OpenLC(StreamStore(), iModelStreamIdSet->EntryManagerStreamId()); + in >> *iEntryManager; + + // Sanity Check here. If the entry stream set is empty, then the store stream ids should + // all be zero - reset them here to ensure that if the file has been partially corrupted, we can + // recover from this. + if ( iModelStreamIdSet->EntryStreamIdSet().Count() == 0 ) + { + iEntryManager->Reset(); + } + + CleanupStack::PopAndDestroy(&in); + } + +void CAgnEntryModel::ResetIndexes() + { + // Reset indexes, returns EFalse if file is empty + iSimpleEntryTable->Reset(); + iCategoryIndex->Reset(); + iAttachmentIndex->Reset(); + iModelStreamIdSet->EntryStreamIdSet().ResetIteratorToStart(); + if (iModelStreamIdSet->EntryStreamIdSet().Count() == 0) + { + iNumStreamsProcessed = 0; + } + } + +// This method is used for generating a filename for the index file from the +// calendar filename. +TBool CAgnEntryModel::GenerateIndexFileName(TFileName& aFileName) + { + aFileName = FileName(); + if ((aFileName.Length() + KIdxFilePostFixLength) > KMaxFileName) + { + iIndexFileIsDirty = ETrue; + iIndexFileIsPresent = EFalse; + return EFalse; + } + aFileName.Append(KIdxFilePostFix); + return ETrue; + } + + +// This method marks the index file as dirty (i.e. out of sync with the indices +// in RAM) by deleting it. A flag is kept internally to allow us to know that the +// file needs to be rebuilt and to no try to delete the file more than once. +void CAgnEntryModel::MarkIndexFileAsDirtyL() + { + if (iIndexFileIsDirty) + { + return; // the file is already marked as dirty + } + + TFileName idxfilename; + if (!GenerateIndexFileName(idxfilename)) + { + User::Leave(KErrBadName); + } + + TInt connectErr = iFs.Connect(); + User::LeaveIfError(connectErr); + + iFs.Delete(idxfilename); // ignore return as there is nothing we can do with it + + iIndexFileIsDirty = ETrue; + } + +// This method allows clients of the model to determine if the index file is +// dirty and therefore in need of being rewritten with the current data. +TBool CAgnEntryModel::IsIndexFileDirty() const + { + return iIndexFileIsDirty; + } + +TCalCollectionId CAgnEntryModel::CollectionId() const + { + return iAgnServerFile->CollectionId(); + } + +// This method reads the indices from the index file. +// It returns: +// ETrue - indices successfully read from file +// EFalse - indices not read from file (file may be missing, or there +// may have been errors trying to read the file. +// It Leaves when any of the index InternalizeL functions Leave. +// Overall description: +// 1. Attempt to open the file +// 2. If file is present InternalizeL all indices and return true +// 4. If no file, or errors in opening or streaming return false +TBool CAgnEntryModel::LoadIndexFileL() + { + TFileName idxfilename; + if (!GenerateIndexFileName(idxfilename)) + { + User::Leave(KErrBadName); + } + + TInt connectErr = iFs.Connect(); + User::LeaveIfError(connectErr); + + RFile idxFile; + TInt errReadIdx = idxFile.Open(iFs, idxfilename, EFileRead); + CleanupClosePushL(idxFile); + + if (errReadIdx == KErrNone) // we have a file + { + RFileReadStream idxStream; + idxStream.Attach(idxFile); + CleanupClosePushL(idxStream); + + TInt internalizeErr = KErrNone; + + TRAP(internalizeErr, iSimpleEntryTable->InternalizeL(idxStream, iTzRuleIndex)); + if (internalizeErr != KErrNone) + { + // clear any entries that may have been added to the table + // before leaving + iSimpleEntryTable->Reset(); + User::Leave(internalizeErr); + } + TRAP(internalizeErr, iCategoryIndex->InternalizeL(idxStream)); + if (internalizeErr != KErrNone) + { + // clear any entries in this index or the entry table + // before leaving + iSimpleEntryTable->Reset(); + iCategoryIndex->Reset(); + User::Leave(internalizeErr); + } + TRAP(internalizeErr, iAttachmentIndex->InternalizeL(idxStream)); + if (internalizeErr !=KErrNone) + { + // clear any entries in this index, the category index + // and the entry table before leaving + iAttachmentIndex->Reset(); + iSimpleEntryTable->Reset(); + iCategoryIndex->Reset(); + User::Leave(internalizeErr); + } + + CleanupStack::PopAndDestroy(2); //idxStream, idxFile + iIndexFileIsDirty = EFalse; + return ETrue; // we have successfully read the index file + } + else if (errReadIdx == KErrNotFound) + { + CleanupStack::PopAndDestroy(); // idxFile + iIndexFileIsDirty = ETrue; // the index file needs to be created/updated + iIndexFileIsPresent = EFalse; // so we won't try to find the file every time + // DoBuildIndexL() is called + return EFalse; // no file to read + } + + // if we get here, then there was an error reading the file for some + // reason other than it not being present. We'll mark it as DIRTY (i.e. delete it). + // MarkIndexFileAsDirtyL will try to delete the file if errors occur. + CleanupStack::PopAndDestroy(&idxFile); + MarkIndexFileAsDirtyL(); + iIndexFileIsPresent = EFalse; + return EFalse; + } + + +// This method attempts to save all indices to the index file. +// If any errors are encountered it will Leave. +// Clients of this method should TRAP the Leave and possibly +// mark the file as dirty or try to delete it. +void CAgnEntryModel::SaveIndexFileL() + { + TFileName idxfilename; + if (!GenerateIndexFileName(idxfilename)) + { + User::Leave(KErrBadName); + } + TInt connectErr = iFs.Connect(); + User::LeaveIfError(connectErr); + + RFile idxFile; + TInt errWriteIdx = idxFile.Replace(iFs, idxfilename, EFileWrite); + User::LeaveIfError(errWriteIdx); + CleanupClosePushL(idxFile); + + + RFileWriteStream idxStream; + idxStream.Attach(idxFile); + CleanupClosePushL(idxStream); + + iSimpleEntryTable->ExternalizeL(idxStream); + iCategoryIndex->ExternalizeL(idxStream); + iAttachmentIndex->ExternalizeL(idxStream); + + CleanupStack::PopAndDestroy(2); //idxStream, idxFile + + iIndexFileIsDirty = EFalse; + } + +TBool CAgnEntryModel::DoLoadIndexFile() + { + // Check to see if we have a valid index file that we can read + TBool readPassed = EFalse; + TRAPD(idxErr, readPassed = LoadIndexFileL()); + if ((readPassed) && (idxErr == KErrNone)) + { + // We successfully read the prebuilt index. + return ETrue; + } + else + { + // something bad happened to the index file + // we need to delete it because it couldn't be read + // To ensure that it is deleted we need to mark the index + // file as "not dirty". + iIndexFileIsDirty = EFalse; + iIndexFileIsPresent = EFalse; + // trap the leave to keep things running, but there is nothing + // we can do if the file can't be deleted. + TRAP_IGNORE(MarkIndexFileAsDirtyL()); + } + return EFalse; + } + + +TInt CAgnEntryModel::DoIndexBuildStepL() + { + // Check to see if we have a valid index file that we can read + // before trying to build all the indices. + + if (iIndexFileIsPresent) + { + if (DoLoadIndexFile()) + { + // We successfully read the prebuilt index. + // There is no need to go any further. + // The 0 below indicates that there is nothing left to do. + return KAgnPercentageComplete; + } + } + // otherwise, there is no file or the file is dirty (out of sync), + // continue to build indexes + + TInt retVal = 0; + + FOREVER + { + TStreamId streamId(0); + + if ( ! iModelStreamIdSet->EntryStreamIdSet().At(streamId) ) // returns value in streamId + { + retVal = KAgnPercentageComplete; // indicate completion if not more streams + break; + } + + RStoreReadStream in; + in.OpenLC(StreamStore(), streamId); + + in.ReadInt8L(); // discard buffer type information + + if ( iCalConverter ) + { + // Read entry from a calendar file whose + // version is not the current one. + + iCalConverter->InternalizeEntriesL(in); + } + else + { + // Read entry from a calendar file - current version + const TUint8 KCount = in.ReadUint8L(); + + CAgnEntry* entry = NULL; + for ( TInt ii = 0; ii < KCount; ++ii ) + { + entry = CAgnEntry::NewL(in); + CleanupStack::PushL(entry); + + UpdateIndexL(*entry, NULL, EBuildIndex); + CleanupStack::PopAndDestroy(entry); + } + } + + CleanupStack::PopAndDestroy(); // in + + iSimpleEntryTable->Commit(); + + if ( ! iModelStreamIdSet->EntryStreamIdSet().Next() ) + { + retVal = KAgnPercentageComplete; // no more streams to process + break; + } + + // check iNumStreamsProcessed is valid - this number is used to calculate percentage complete + ++iNumStreamsProcessed; + __ASSERT_ALWAYS(iNumStreamsProcessed <= iModelStreamIdSet->EntryStreamIdSet().Count(), User::Leave(KErrCorrupt)); + + // After every second stream is processed, calculate the percentage complete and return + if ( iNumStreamsProcessed % 2 == 0 ) + { + // coverity[check_return] coverity[unchecked_value] + TInt percentage = (iNumStreamsProcessed * KAgnPercentageComplete) / iModelStreamIdSet->EntryStreamIdSet().Count(); + retVal = (percentage < 1 ? 1 : percentage); // percentage complete must be at least 1 (returning 0 indicates index building complete) + break; + } + } + + if (retVal == KAgnPercentageComplete) + { + // A return value of 0 indicates that the indexes have been + // completely built. We will save them to file now so that + // no future errors will cause this information to need to + // be built again. + TRAPD (saveErr, SaveIndexFileL()); + if (saveErr != KErrNone) + { + // We couldn't save the index file, so we'll mark it as dirty + TRAPD(ignore,MarkIndexFileAsDirtyL()); + User::LeaveIfError(ignore); + } + } + + return (retVal); + } + + +void CAgnEntryModel::BuildIndexCompleteL() + { + if ( iCalConverter ) + { + RestoreCategoriesL(); + iCalConverter->CompleteConversionL(); + delete iCalConverter; + iCalConverter = NULL; + } + + CreateAlarmForServerL(); + iAlarm->DeleteAllAlarms(); + iAlarm->FindAndQueueNextAlarmL(EFalse); + } + + +TInt CAgnEntryModel::MatchExactText(const TDesC& aTextField, const TDesC& aSearchText) + { + return aTextField.Match(aSearchText); + } + +TInt CAgnEntryModel::MatchFoldedText(const TDesC& aTextField, const TDesC& aSearchText) + { + return aTextField.MatchC(aSearchText); + } + +TBool CAgnEntryModel::MatchSearchTextL(MatchTextFnPtr aMatchTextFunction, CAgnEntry& aEntry, const TDesC& aSearchText, const TAgnFilter& aFilter) + { + // always search summary + TInt pos = aMatchTextFunction(aEntry.Summary(), aSearchText); + + if ( pos == KErrNotFound && aFilter.IsEntryLocationSearched() ) + { + pos = aMatchTextFunction(aEntry.Location(), aSearchText); + } + if ( pos == KErrNotFound && aFilter.IsEntryDescriptionSearched() ) + { + pos = aMatchTextFunction(aEntry.Description(), aSearchText); + } + + CAgnAttendee* organizer = aEntry.Organizer(); + const TInt KNumAttendees = aEntry.AttendeeCount(); + + if ( organizer && pos == KErrNotFound ) + { + if ( aFilter.IsOrganizerAddressSearched() ) + { + pos = aMatchTextFunction(organizer->Address(), aSearchText); + } + if ( pos == KErrNotFound && aFilter.IsOrganizerSentByAddressSearched() ) + { + pos = aMatchTextFunction(organizer->SentBy(), aSearchText); + } + if ( pos == KErrNotFound && aFilter.IsOrganizerCommonNameSearched() ) + { + pos = aMatchTextFunction(organizer->CommonName(), aSearchText); + } + } + + for ( TInt i = 0; pos == KErrNotFound && i < KNumAttendees; ++i) + { + CAgnAttendee& attendee = aEntry.FetchAttendee(i); + + if ( aFilter.IsAttendeeAddressSearched() ) + { + pos = aMatchTextFunction(attendee.Address(), aSearchText); + } + if ( pos == KErrNotFound && aFilter.IsAttendeeSentByAddressSearched() ) + { + pos = aMatchTextFunction(attendee.SentBy(), aSearchText); + } + if ( pos == KErrNotFound && aFilter.IsAttendeeCommonNameSearched() ) + { + pos = aMatchTextFunction(attendee.CommonName(), aSearchText); + } + } + + return (pos >= 0); + } + +TBool CAgnEntryModel::MatchFullEntryL(const TAgnEntryId& aEntryId, const TFindInstanceParams& aSearchParams) + { + TBool match(ETrue); + if(aSearchParams.iSearchString.Length() > 0) + { + CAgnEntry* entry = FetchEntryL(aEntryId); + __ASSERT_ALWAYS(entry, User::Leave(KErrNotFound)); + + CleanupStack::PushL(entry); + if(!MatchSearchTextL(*entry, aSearchParams.iSearchString, aSearchParams.iFilter)) + { + match = EFalse; + } + CleanupStack::PopAndDestroy(entry); + } + return match; + } + +TBool CAgnEntryModel::MatchSearchTextL(CAgnEntry& aEntry, const TDesC& aSearchText, const TAgnFilter& aFilter) + { + TBool matchText = ETrue; + + if ( aSearchText.Length() > 0 ) + { + TBuf<256> searchString; + _LIT(KWildCard, "*"); + searchString.Append(KWildCard); + searchString.Append(aSearchText); + searchString.Append(KWildCard); + + if ( aEntry.Summary() == KNullDesC && aEntry.SummaryStreamId() != KNullStreamId ) + { + HBufC* summary = RestoreTextL(aEntry.SummaryStreamId()); + aEntry.SetSummary(summary); + } + + // load description if required + if ( aEntry.Description() == KNullDesC && aEntry.DescriptionStreamId() != KNullStreamId && aFilter.IsEntryDescriptionSearched() ) + { + HBufC* description = RestoreTextL(aEntry.DescriptionStreamId()); + aEntry.SetDescription(description); + } + + MatchTextFnPtr matchFn = &MatchFoldedText; + if ( aFilter.IsExactTextOnlySearch() ) + { + matchFn = &MatchExactText; + } + + matchText = MatchSearchTextL(matchFn, aEntry, searchString, aFilter); + } + + return (matchText); + } + +void CAgnEntryModel::FindInstancesL(CArrayFix& aInstances, const TFindInstanceParams& aParameters) + { + iExtractor->FindInstancesL(aInstances, aParameters); + +#if defined (__CAL_INSTANCE_LOGGING__) || (__CAL_VERBOSE_LOGGING__) + LogInstanceSearchL(aParameters, aInstances); +#endif + } + +void CAgnEntryModel::LogInstanceSearchL(const TFindInstanceParams& aParameters, const CArrayFix& aInstances) const + { + TAgnFilter iFilter; + TTime dbgStartTime; + TTime iEndTime; + TBuf iSearchString; + + TAgnFilter debugFilter = aParameters.iFilter; + + TBuf<64> filterBuf; + + if (debugFilter.AreTimedApptsIncluded() && + debugFilter.AreRemindersIncluded() && + debugFilter.AreEventsIncluded() && + debugFilter.AreAnnivsIncluded() && + debugFilter.AreCompletedTodosIncluded() && + debugFilter.AreIncompletedTodosIncluded() ) + { + filterBuf.Copy(_L("All entries")); + } + else + { + if (debugFilter.AreTimedApptsIncluded()) + { + filterBuf.Append(_L("Appts,")); + } + if (debugFilter.AreRemindersIncluded()) + { + filterBuf.Append(_L("Reminders,")); + } + if (debugFilter.AreEventsIncluded()) + { + filterBuf.Append(_L("Events,")); + } + if (debugFilter.AreAnnivsIncluded()) + { + filterBuf.Append(_L("Annivs,")); + } + if (debugFilter.AreCompletedTodosIncluded() && debugFilter.AreIncompletedTodosIncluded()) + { + filterBuf.Append(_L("TODOs,")); + } + else if (debugFilter.AreIncompletedTodosIncluded()) + { + filterBuf.Append(_L("Incomplete TODOs,")); + } + else if (debugFilter.AreCompletedTodosIncluded()) + { + filterBuf.Append(_L("Complete TODOs,")); + } + } + + if (aParameters.iSearchString.Length() > 0) + { + TBuf searchBuf; + searchBuf.Copy(aParameters.iSearchString); + AgmDebug::DebugLog("Searching for text %S", &searchBuf); + } + + TBuf startTimeBuf; + TBuf endTimeBuf; + + AgmDebug::TTimeStrL(aParameters.iRangeStart.LocalL(),startTimeBuf); + AgmDebug::TTimeStrL(aParameters.iRangeEnd.LocalL(),endTimeBuf); + + const TInt KInstanceCount(aInstances.Count()); + + AgmDebug::DebugLog("FindInstancesL: Range: Start - %S, End - %S, Filter '%S'", &startTimeBuf, &endTimeBuf, &filterBuf); + AgmDebug::DebugLog("Found %d instances", KInstanceCount); + + for ( TInt i = 0; i < KInstanceCount; ++i ) + { + TBuf instanceTimeBuf; + AgmDebug::TTimeStrL(aInstances[i].InstanceIdL().Date().LocalL(), instanceTimeBuf); + AgmDebug::DebugLog("Found instance: %S", &instanceTimeBuf); + } + + } + +void CAgnEntryModel::CreateAlarmForServerL() + { + if ( ! iAlarm ) + { + iAlarm = CAgnAlarm::NewL(this, NULL); + } + } + +/** +Get list of ids of alarmed entries in the next 60 days. +FindInstanceL is used to find instances of alarmed entries. +*/ +void CAgnEntryModel::NextAlarmForServerL(const TTime& aNow, CArrayFixFlat& aAlarmedIds) + { + if ( ! AgnDateTime::IsValidAgendaTTime(aNow) || ! iSimpleEntryTable || iAgnServerFile->IsFileDisabled()) + { + return; + } + + CArrayFixSeg* dayInfoList = new(ELeave) CArrayFixSeg(4); + CleanupStack::PushL(dayInfoList); + + TFindInstanceParams searchParams; + searchParams.iUndatedTodoTimeLocal = aNow; + searchParams.iFilter = TAgnFilter(CalCommon::EIncludeAppts|CalCommon::EIncludeReminder|CalCommon::EIncludeEvents| + CalCommon::EIncludeAnnivs|CalCommon::EIncludeIncompletedTodos|CalCommon::EIncludeAlarmedOnly, CalCommon::EFoldedTextSearch); + searchParams.iSearchString = KNullDesC(); + searchParams.iRangeStart.SetLocalL(AgnDateTime::ResetToMidnight(aNow) - TTimeIntervalDays(1)); // alarms can be up to 24 hours after start time so check this + searchParams.iRangeEnd.SetLocalL(aNow + TTimeIntervalDays(2)); + + iExtractor->FindInstancesL(*dayInfoList, searchParams); + TAgnDaySortKey sortKey(AgnDateTime::MaxDate(), searchParams.iUndatedTodoTimeLocal); + dayInfoList->Sort(sortKey); + + + searchParams.iFilter = TAgnFilter(CalCommon::EIncludeAppts|CalCommon::EIncludeReminder|CalCommon::EIncludeEvents| + CalCommon::EIncludeAnnivs|CalCommon::EIncludeIncompletedTodos|CalCommon::EIncludeAlarmedOnly| + CalCommon::EIncludeRptsNextInstanceOnly, CalCommon::EFoldedTextSearch); + + // check the next month if no alarmed instances found + const TTime KLimit = aNow + TTimeIntervalDays(60); + + while ( dayInfoList->Count() == 0 && searchParams.iRangeStart.LocalL() < KLimit ) + { + searchParams.iRangeStart.SetLocalL(searchParams.iRangeEnd.LocalL()); + searchParams.iRangeEnd.SetLocalL(searchParams.iRangeStart.LocalL() + TTimeIntervalDays(10)); + + iExtractor->FindInstancesL(*dayInfoList, searchParams); + TAgnDaySortKey sortKey1(AgnDateTime::MaxDate(), searchParams.iUndatedTodoTimeLocal); + dayInfoList->Sort(sortKey1); + } + + UpdateAlarmListL(aAlarmedIds, *dayInfoList, aNow); + + CleanupStack::PopAndDestroy(); //dayInfoList + } + +/** +Examine the contents of aDayInfoList and see if the any of the contained alarm instances should be added to +or replace the contents of aAlarmedIds +@internalComponent +*/ + +void CAgnEntryModel::UpdateAlarmListL(CArrayFixFlat& aAlarmedIds,CArrayFixSeg& aDayInfoList, const TTime& aNow) + { + TTime nextAlarmLocal(AgnDateTime::MaxDate()); + + for ( TInt ii = aDayInfoList.Count() - 1; ii >= 0; --ii ) + { + TAgnSortInstance sortInstance = aDayInfoList[ii]; + + if ( sortInstance.SimpleEntry().Type() == CCalEntry::ETodo || sortInstance.iStartTimeLocal >= AgnDateTime::ResetToMidnight(aNow) ) + { + TTime alarmTimeLocal(sortInstance.InstanceAlarmDateTime()); + + if ( alarmTimeLocal <= aNow && sortInstance.SimpleEntry().RptDef() ) + { + TTime nextInstance; + + while ( sortInstance.SimpleEntry().RptDef()->NudgeNextInstanceL(sortInstance.InstanceDate(), nextInstance, ETrue) && nextInstance <= aNow) + { + sortInstance.SetL(nextInstance, aNow); + + if ( sortInstance.iStartTimeLocal < AgnDateTime::MaxDate() ) + { + alarmTimeLocal = sortInstance.InstanceAlarmDateTime(); + } + } + } + + if ( AgnDateTime::IsValidAgendaTTime(alarmTimeLocal) ) + { + if ( alarmTimeLocal > aNow && alarmTimeLocal < nextAlarmLocal ) + { + aAlarmedIds.Reset(); + aAlarmedIds.AppendL(sortInstance); + nextAlarmLocal = alarmTimeLocal; + } + else + { + if ( alarmTimeLocal == nextAlarmLocal ) + { + aAlarmedIds.AppendL(sortInstance); + } + } + } + } + } + } + +/** Schedules a list of alarms whose dateTime meets the following criteria: +alarmTime >= aCurrentTime AND alarmTime <= aCurrentTime + 30 days +@internalComponent +*/ + void CAgnEntryModel::NextFewAlarmsForServerL(const TTime& aStartDateTime,const TTime& aEndDateTime, + CArrayFixFlat& aAlarmedIds,const TInt aMaxNumberOfAlarms) + { + if(iAgnServerFile->IsFileDisabled()) + { + return; + } + if ( AgnDateTime::IsValidAgendaTTime(aStartDateTime) && AgnDateTime::IsValidAgendaTTime(aEndDateTime) && + aEndDateTime>=aStartDateTime ) + { + CArrayFixSeg* dayInfoList = new(ELeave) CArrayFixSeg(4); + CleanupStack::PushL(dayInfoList); + + TFindInstanceParams searchParams; + searchParams.iUndatedTodoTimeLocal = searchParams.iRangeStart.LocalL(); + searchParams.iFilter = TAgnFilter(CalCommon::EIncludeAppts|CalCommon::EIncludeReminder|CalCommon::EIncludeEvents| + CalCommon::EIncludeAnnivs|CalCommon::EIncludeIncompletedTodos|CalCommon::EIncludeAlarmedOnly| + CalCommon::EIncludeRptsNextInstanceOnly, CalCommon::EFoldedTextSearch); + searchParams.iSearchString = KNullDesC(); + searchParams.iRangeStart.SetLocalL(aStartDateTime - TTimeIntervalDays(1)); // alarms can be up to 24 hours after start time so check this + searchParams.iRangeEnd.SetLocalL(aEndDateTime); + + iExtractor->FindInstancesL(*dayInfoList, searchParams); + TAgnAlarmSortKey aKey; + dayInfoList->Sort(aKey); + + AddToAlarmListL(aAlarmedIds, *dayInfoList, aStartDateTime, aEndDateTime, aMaxNumberOfAlarms); + + CleanupStack::PopAndDestroy(dayInfoList); + } + } + + +void CAgnEntryModel::AddToAlarmListL(CArrayFixFlat& aAlarmedIds,CArrayFixSeg& aDayInfoList, const TTime& aStartDateTime, + const TTime& aEndDateTime,const TInt ) +// +// Examine the contents of aDayInfoList add to aAlarmedIds +// + { + const TInt KDayListCount(aDayInfoList.Count()); + for (TInt i = 0; i < KDayListCount; ++i) + { + const TAgnSortInstance KSortInstance = aDayInfoList[i]; + const TTime KAlarmInstance(KSortInstance.InstanceAlarmDateTime()); + + if (KAlarmInstance > aStartDateTime && KAlarmInstance <= aEndDateTime) + { + aAlarmedIds.AppendL(KSortInstance); + } + } + } + +void CAgnEntryModel::FindAndQueueNextFewAlarmsL() + { + if ( iAlarm ) + { + iAlarm->FindAndQueueNextFewAlarmsL(); + } + } + +#ifdef SYMBIAN_SYSTEM_STATE_MANAGEMENT +void CAgnEntryModel::DeleteAlarmsAndRequeueSessionAlarmL() + { + if ( iAlarm ) + { + iAlarm->DeleteAlarmsAndRequeueSessionAlarmL(); + } + } +#endif + +/** +Return next time (from aStartDate) on which an instance exists +@internalComponent +*/ +void CAgnEntryModel::NextPossibleInstancesL(CArrayFix& aInstances, const TFindInstanceParams& aSearchParams) const + { + iExtractor->NextPossibleInstancesL(aInstances, aSearchParams); + } +/** +Return previous time (from aStartDate) on which an instance exists +@internalComponent +*/ +void CAgnEntryModel::PreviousPossibleInstancesL(CArrayFix& aInstances, const TFindInstanceParams& aSearchParams) const + { + iExtractor->PreviousPossibleInstancesL(aInstances, aSearchParams); + } + + +// Place the uids of entries that have a last changed data greater than aDate and which meet the selection +// criteria specified in aFilter into the aUids array. +// +void CAgnEntryModel::GetEntryUidsSinceDateL(const TTime& aTime, RArray& aUniqueIdList) + { + iSimpleEntryTable->FindByLastModifiedDateUtcL(aTime, aUniqueIdList); + } + + +/** +Get the file ID of the currently open Agenda file. +This is unique to the file. +@capability None +*/ +const TInt64& CAgnEntryModel::GetFileIdL() + { + if ( iFileId == 0 ) + { + RStoreReadStream in; + in.OpenLC(StreamStore(),iModelStreamIdSet->FileInformationStreamId()); + TInt64 fileId=0; + in >> fileId; + CleanupStack::PopAndDestroy(); //in + iFileId = fileId; + } + + return iFileId; + } + + +HBufC* CAgnEntryModel::RestoreTextL(const TStreamId& aStream) + { + CStreamStore& store = StreamStore(); + RStoreReadStream in; + in.OpenLC(store, aStream); + TInt textLength = in.ReadUint32L(); + HBufC* text = HBufC::NewL(in, textLength); + CleanupStack::PopAndDestroy(); //in + return text; + } + +TStreamId CAgnEntryModel::StoreTextL(const TDesC& aText) + { + CStreamStore& store = StreamStore(); + RStoreWriteStream out; + TStreamId id = out.CreateLC(store); + out.WriteUint32L(aText.Length()); + out << aText; + out.CommitL(); + CleanupStack::PopAndDestroy(); //out + return id; + } + +/* + * Updates the notes text stored in the specified stream. Null Descriptors are expected to be + * handled client side + */ +void CAgnEntryModel::UpdateTextL(const TDesC& aText, const TStreamId& aStream) + { + __ASSERT_DEBUG(aText.Length() != 0, Panic(EAgmNullDescriptor)); + + CStreamStore& store = StreamStore(); + RStoreWriteStream out; + out.ReplaceLC(store, aStream); + out.WriteUint32L(aText.Length()); + out << aText; + out.CommitL(); + CleanupStack::PopAndDestroy(); //out + } + +/* + * Deletes the specified stream holding notes data. + * + */ +void CAgnEntryModel::DeleteTextStreamL(const TStreamId& aStream) + { + StreamStore().DeleteL(aStream); + } + + +CAgnContent* CAgnEntryModel::RestoreAlarmActionL(const TStreamId& aStream) + { + CStreamStore& store = StreamStore(); + RStoreReadStream in; + in.OpenLC(store, aStream); + + CAgnContent* alarmAction = new (ELeave) CAgnContent; + CleanupStack::PushL(alarmAction); + in >> *alarmAction; + CleanupStack::Pop(alarmAction); + + CleanupStack::PopAndDestroy(); //in + return alarmAction; + } + + +TStreamId CAgnEntryModel::StoreAlarmActionL(const CAgnContent& aAlarmAction) + { + CStreamStore& store = StreamStore(); + RStoreWriteStream out; + TStreamId id = out.CreateLC(store); + out << aAlarmAction; + out.CommitL(); + CleanupStack::PopAndDestroy(); //out + + return id; + } + +/* + * Updates the rich alarm data stored in the specified stream. + */ +void CAgnEntryModel::UpdateAlarmActionL(const CAgnContent& aAlarmAction, const TStreamId& aStream) + { + CStreamStore& store = StreamStore(); + RStoreWriteStream out; + out.ReplaceLC(store, aStream); + out << aAlarmAction; + out.CommitL(); + CleanupStack::PopAndDestroy(); //out + } + +/* + * Deletes the specified stream holding rich alarm data. + * + */ +void CAgnEntryModel::DeleteAlarmActionStreamL(const TStreamId& aStream) + { + StreamStore().DeleteL(aStream); + } + +const TDesC& CAgnEntryModel::FileName() const + { + return iAgnServerFile->FileName(); + } + +void CAgnEntryModel::SetUpdateAlarmL(TBool aUpdateAlarm) + { + iUpdateAlarm = aUpdateAlarm; + + if ( iUpdateAlarm ) + { + iAlarm->FindAndQueueNextAlarmL(EFalse); + } + } + + +/** Commits any changes both to file and internally that have occurred to the model. + +This function does not empty the buffers. Call FlushL to do that. +@capability None +*/ +void CAgnEntryModel::DoCommitL() + { + + if(!AgnServFile().IsBackupRestoreLock()) + { + iModelStreamIdSet->CommitL(StreamStore()); + StreamStore().CommitL(); + iSimpleEntryTable->Commit(); + iAttachmentIndex->CommitL(*iAgnServerFile); + } + + // Trigger compacting after a certain number of operations on the model + if(iOperationsCounter >= KCompactOperationsThreshold) + { + // Initiate synchronous compact + iAgnServerFile->CompactFileL(); + // Reset operations counter + iOperationsCounter=0; + } + } + +// Commits all changes to file. +void CAgnEntryModel::CommitL() + { + DoCommitL(); + ResetRollback(); + } + +// Called after multiple entries have been deleted. +// This can fail at any time and must roll back, so notification cannot happen until the changes are committed to file. +// This function does the commits then notifies, using the rollback array to find which entries have been deleted. +void CAgnEntryModel::CommitAndNotifyDeletesL(TAgnChangeFilter& aChangeFilter) + { + DoCommitL(); + iChangeFilter = &aChangeFilter; + + const TInt KDeleteCount = iDeleteRollbackArray.Count(); + for (TInt i = 0; i < KDeleteCount; ++i) + { + CAgnEntry* deletedEntry = iDeleteRollbackArray[i]; + NotifyingL(MCalChangeCallBack2::EChangeDelete, *deletedEntry, NULL); + + if(iTzRuleIndex) + { + //Remove the tz rule from tz rule index + //we have to do it after CAgnEntryModel::NotifyingL that is indirectly using the + //tz rule in aEntry. + iTzRuleIndex->RemoveTzRuleL(*deletedEntry); + } + } + + StreamStore().CommitL(); + ResetRollback(); + } + +// Add an entry to a rollback array. +// If aAdd is ETrue it is added to the add rollback array (for add operations) +// If aAdd is EFalse it is added to the delete rollback array (for delete operations) +void CAgnEntryModel::AppendRollbackArrayL(const CAgnEntry& aEntry, TBool aAdd) + { + if ( aAdd ) + { + iAddRollbackArray.AppendL(aEntry.EntryId()); + } + else + { + CAgnEntry* entryCopy = aEntry.CloneL(); + CleanupStack::PushL(entryCopy); + iDeleteRollbackArray.AppendL(entryCopy); + CleanupStack::Pop(entryCopy); + } + } + +// Reset rollback arrays +void CAgnEntryModel::ResetRollback() + { + iAddRollbackArray.Reset(); + iDeleteRollbackArray.ResetAndDestroy(); + } + +/** +Rollback indexes in RAM. This is done by deleting all added entries and re-adding all deleted entries +@internalComponent +*/ +void CAgnEntryModel::RollbackIndexesL() + { + // delete all added entries + for ( TInt ii = iAddRollbackArray.Count() - 1; ii >= 0; --ii ) + { + const TAgnEntryId& KEntryIdToDelete = iAddRollbackArray[ii]; + + CAgnSimpleEntry* entryToDelete = iSimpleEntryTable->GetEntry(KEntryIdToDelete); + if(entryToDelete != NULL) + { + iSimpleEntryTable->DeleteEntry(KEntryIdToDelete); + } + + iAddRollbackArray.Remove(ii); + } + + iAddRollbackArray.Reset(); + + if(iTzRuleIndex) + { + //Rollback the reference count of tz rules in tz rule index + TRAPD(ret, iTzRuleIndex->RollBackL()); + if(ret != KErrNotReady) + { + User::LeaveIfError(ret); + } + } + + // re-add all deleted entries + for ( TInt ii = iDeleteRollbackArray.Count() - 1; ii >= 0; --ii ) + { + CAgnEntry* entryToAdd = iDeleteRollbackArray[ii]; + + // Check the entry has a guid hash + if ( entryToAdd->GsDataType() == CGsData::EParent && ! entryToAdd->GuidHash() ) + { + entryToAdd->SetGuidHash( GenerateHash8L(entryToAdd->Guid()) ); + } + + // If the entry has already been added, delete it to prevent an error from re-adding the same entry. + // This can happen if the delete operation fails at a certain point. + const TAgnEntryId& KEntryIdToAdd = entryToAdd->EntryId(); + if (iSimpleEntryTable->GetEntry(KEntryIdToAdd)) + { + iSimpleEntryTable->DeleteEntry(KEntryIdToAdd); + } + + if(iTzRuleIndex) + { + //Fetch back the tz rule + iTzRuleIndex->FetchTzRuleL(*entryToAdd); + } + + // re-add the entry + AddEntryToIndexesL(*entryToAdd); + + iDeleteRollbackArray.Remove(ii); + delete entryToAdd; + } + + iDeleteRollbackArray.Reset(); + + iAttachmentIndex->Rollback(); + } + +/** Reverts the model to the state it was in after CommitL() or RollbackL() was +last called. This reverts changes to the file and to the indexes held in RAM. + +This means that it deletes all entries which have been added, and reinstates +all entries which have been deleted. + +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 +Rollback was called. + +@internalComponent +*/ +void CAgnEntryModel::Rollback() + { + iEntryManager->Reset(); + + StreamStore().Revert(); + + TRAPD(ret,iModelStreamIdSet->RollbackL()); + __ASSERT_DEBUG(ret==KErrNone, Panic(EAgmErrRollbackFailed)); + + TRAP(ret,RollbackIndexesL()); + } + + +void CAgnEntryModel::NotifyingL(MCalChangeCallBack2::TChangeType aChangeType, CAgnEntry& aEntry, CAgnInstanceInfo* aOriginalEntry) + { + if ( iChangeFilter ) + { + if ( iChangeFilter->ChangeBroadcastEnabled() ) + { + NotifyChangeL((iChangeFilter->Session()), &aEntry, aChangeType, aOriginalEntry); + } + else + { + iChangeFilter->SetChangeMadeWhileDisabled(ETrue); + } + + if ( aEntry.Type() == CCalEntry::ETodo ) + { + iChangeFilter->SetPubSubChange(TAgnChangeFilter::ETodoChanged); + } + else + { + iChangeFilter->SetPubSubChange(TAgnChangeFilter::EEventChanged); + } + + NotifyPublishAndSubscribeL(*iChangeFilter); + } + } + +/* +Delete aEntry from the store. If it has a positive replicated count however then mark it as having +been deleted and update it instead. +@capability WriteUserData +@capability ReadUserData +*/ +void CAgnEntryModel::DeleteEntryL(CAgnEntry& aEntry, TBool aCascadeDelete, TAgnChangeFilter* aChangeFilter) + { + #if defined (__CAL_ENTRY_LOGGING__) || (__CAL_VERBOSE_LOGGING__) + { + TBuf guidBuf; + guidBuf.Copy(aEntry.Guid()); + AgmDebug::DebugLog("DeleteEntryL: Deleting entry with local UID=%d, GUID=%S", aEntry.LocalUid(), &guidBuf); + } + #endif + + iChangeFilter = aChangeFilter; + + if ( aCascadeDelete ) + { + if ( aEntry.GsDataType() == CGsData::EParent ) + { + _DBGLOG_ENTRY(AgmDebug::DebugLog("DeleteEntryL: Deleting children");) + + DeleteChildrenL(aEntry); + } + else + { + _DBGLOG_ENTRY(AgmDebug::DebugLog("DeleteEntryL: Updating Parent");) + UpdateParentL(aEntry); + } + } + + DoDeleteEntryL(aEntry); + + NotifyingL(MCalChangeCallBack2::EChangeDelete,aEntry, NULL); + + if ( iUpdateAlarm && aEntry.HasAlarm() ) + { + iAlarm->FindAndQueueNextAlarmL(EFalse); + iAlarm->DeleteEntriesAlarmL(aEntry.EntryId()); + } + + if(iChangeFilter && iTzRuleIndex) + { + //Remove the tz rule from tz rule index + //we have to do it after CAgnEntryModel::NotifyingL that is indirectly using the + //tz rule in aEntry. + iTzRuleIndex->RemoveTzRuleL(aEntry); + } + } + + +void CAgnEntryModel::DeleteChildrenL(CAgnEntry& aParent) + {//Get Child ids + __ASSERT_DEBUG(aParent.GsDataType() == CGsData::EParent, Panic(EAgmErrNotParentEntry)); + + CAgnEntry* parent = FetchEntryL(aParent.EntryId()); + if (parent != NULL) + { + CleanupStack::PushL(parent); + + const RArray& KIds = parent->ChildIds(); + // delete each child entry + for ( TInt i = KIds.Count() - 1; i >= 0; --i ) + { + const TCalLocalUid& KChildId = KIds[i].ChildId(); + CAgnEntry* childEntry = FetchEntryL(KChildId); // pass flag so as not to tell parent + if (childEntry) + { + CleanupStack::PushL(childEntry); + + DeleteEntryL(*childEntry, EFalse, iChangeFilter); // don't propogate the delete back to this parent + + CleanupStack::PopAndDestroy(childEntry); + } + + aParent.RemoveChildId(KChildId); + } + + CleanupStack::PopAndDestroy(parent); + } + } + + +void CAgnEntryModel::UpdateParentL(CAgnEntry& aChild) + { + // It should be used in server side so that the notification of updating a parent is not sent + __ASSERT_DEBUG(aChild.GsDataType() == CGsData::EChild, Panic(EAgmErrNotChildEntry)); + + // get parent and update + CAgnEntry* parentEntry = FetchEntryL(aChild.ParentId()); + __ASSERT_ALWAYS(parentEntry, User::Leave(KErrNotFound)); + CleanupStack::PushL(parentEntry); + + parentEntry->RemoveChildId(aChild.LocalUid()); + + UpdateEntryL(*parentEntry, iChangeFilter, EFalse); + + CleanupStack::PopAndDestroy(parentEntry); + } + + +/** +Delete aEntry from the store. If the entry is a todo then its id is removed from its +todo list. +*/ +void CAgnEntryModel::DoDeleteEntryL(CAgnEntry& aEntry) + { + DeleteExternalAttributesL(aEntry); + + TAgnEntryId id = aEntry.EntryId(); + TStreamId streamId = iEntryManager->DeleteEntryL(id); + + if ( streamId != KNullStreamId ) + { + __ASSERT_DEBUG(streamId == aEntry.EntryId().StreamId(), Panic(EAgmErrWrongEntryDeleted)); + StreamStore().DeleteL(streamId); + iModelStreamIdSet->EntryStreamIdSet().DeleteL(streamId); + } + + if ( ! iEntryManager->BufferedDeleting() || iEntryManager->BufferHasBeenStored() ) // during tidying only commit when the buffer has been written + { + iEntryManager->StoreBuffersL(); + ExternalizeEntryManagerL(); + // Don't commit on delete. CommitL is called from CalInterimAPI after a number have been added. + } + + UpdateIndexL(aEntry, NULL, EDelete); + } + + +TBool CAgnEntryModel::EntryHasNoChildrenAndNoValidInstancesL(CAgnEntry& aEntry) const + { + TInt instances(1); + TInt exceptions(0); + if ( aEntry.RptDef() ) + { + // Purely based on repeat rule, does not include exceptions' count + instances = aEntry.RptDef()->InstanceCountL(); + const RArray* KExceptionList = aEntry.RptDef()->Exceptions(); + if (KExceptionList) + { + exceptions += KExceptionList->Count(); + } + + __ASSERT_ALWAYS(instances >= exceptions, User::Leave(KErrCorrupt)); + } + + TBool entryHasNoChild = ((aEntry.GsDataType() == CGsData::EChild) || aEntry.ChildIds().Count() == 0); + return (entryHasNoChild && instances == exceptions); + } + +/* +@capability ReadUserData +@capability WriteUserData +*/ +void CAgnEntryModel::UpdateEntryL(CAgnEntry& aEntry, TAgnChangeFilter* aChangeFilter, TBool aDeleteChildren) + { + TAgnEntryId originalId = aEntry.EntryId(); + + if (originalId.IsNullId()) + { + _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: EntryId is null. Must be a newly created entry");) + + // Only parent entries can be updated + if(aEntry.GsDataType() != CGsData::EParent) + { + _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: KErrArgument: Only parent entries can be updated");) + User::Leave(KErrArgument); + } + + RPointerArray entriesWithThisGuid; + CleanupResetAndDestroyPushL(entriesWithThisGuid); + FetchEntriesL(aEntry.Guid(), entriesWithThisGuid); + + // Only an existing entry with the same guid can be updated + if(entriesWithThisGuid.Count() == 0) + { + _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: KErrNotFound: Only an existing entry with the same guid can be updated");) + User::Leave(KErrNotFound); + } + + CAgnEntry* existingParent = entriesWithThisGuid[0]; + aEntry.SetLocalUid(existingParent->LocalUid()); + aEntry.SetEntryId(existingParent->EntryId()); + originalId = existingParent->EntryId(); + + _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: Setting Ids: LocalUid - %d, EntryId - %d", aEntry.LocalUid(),aEntry.EntryId().Value());) + CleanupStack::PopAndDestroy(&entriesWithThisGuid); + } + + //client server calls needs to be updated + iChangeFilter = aChangeFilter; + + if (EntryHasNoChildrenAndNoValidInstancesL(aEntry)) + { + _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: Deleting invalid entry - No children and no valid instances");) + DeleteEntryL(aEntry, EFalse, iChangeFilter); + return; + } + + if ( aDeleteChildren ) + { + _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: Deleting children");) + DeleteChildrenL(aEntry); + } + + CAgnEntry* oldEntry = FetchEntryL(originalId); + __ASSERT_DEBUG(oldEntry, User::Leave(KErrNotFound)); + CleanupStack::PushL(oldEntry); + TBool hadAlarm = EFalse; + if ( oldEntry ) + { + hadAlarm = oldEntry->HasAlarm(); + } + + CAgnInstanceInfo* instanceInfoBefore = CAgnInstanceInfo::NewLC(*oldEntry); + + TRAPD(ret, DoUpdateEntryL(aEntry, oldEntry)); + + if ( ret != KErrNone ) + { + aEntry.SetEntryId(originalId); + _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: DoUpdateEntryL failed: Leaving with error - %d",ret);) + User::Leave(ret); + } + + NotifyingL(MCalChangeCallBack2::EChangeModify, aEntry, instanceInfoBefore); + + CleanupStack::PopAndDestroy(instanceInfoBefore); + CleanupStack::PopAndDestroy(oldEntry); + + // Delete the old alarm and if a new alarm exists it will be added by findAndQueue + // If a todo is completed or an event updated, while alarm is snoozed, + // then the snoozed alarm has to be deleted separately, and deletion is not handled by findAndQueue + if (hadAlarm) + { + _DBGLOG_ENTRY(AgmDebug::DebugLog("UpdateEntryL: Deleting the alarm on the existing entry");) + + iAlarm->DeleteEntriesAlarmL(aEntry.EntryId()); + } + if (iUpdateAlarm && aEntry.HasAlarm()) + { + iAlarm->FindAndQueueNextAlarmL(EFalse); + } + } + +/** Update an entry in the store. +@capability ReadUserData +*/ +void CAgnEntryModel::DoUpdateEntryL(CAgnEntry& aEntry, CAgnEntry* aOldEntry) + { + TStreamId newStreamId; + UpdateExternalAttributesL(aEntry); + + if (aOldEntry) + { + DoUpdateAttachmentsL(aEntry, *aOldEntry); + } + + if(iTzRuleIndex) + { + __ASSERT_DEBUG(aOldEntry, Panic(EAgmErrNullPointer)); + iTzRuleIndex->UpdateTzRuleL(*aOldEntry, aEntry); + } + + TStreamId oldStreamId = iEntryManager->UpdateEntryL(aEntry, newStreamId); + + if ( oldStreamId != KNullStreamId ) + { + StreamStore().DeleteL(oldStreamId); + iModelStreamIdSet->EntryStreamIdSet().DeleteL(oldStreamId); + } + + if ( newStreamId != KNullStreamId ) + { + iModelStreamIdSet->EntryStreamIdSet().AddL(newStreamId); + } + + iEntryManager->StoreBuffersL(); + ExternalizeEntryManagerL(); + + UpdateIndexL(aEntry, aOldEntry, EUpdate); + } + +// Called when an entry is updated +// This compares the new entry with the old one to see if any attachments have changed drive (by calling CCalAttachmentFile::SetDrive). +void CAgnEntryModel::DoUpdateAttachmentsL(CAgnEntry& aNewEntry, CAgnEntry& aOldEntry) + {//This method will move the attachment to a different drive if it has been reset by the user. + const TInt KOldAttachmentCount = aOldEntry.AttachmentCount(); + const TInt KNewAttachmentCount = aNewEntry.AttachmentCount(); + + for (TInt oldEntryIndex = 0; oldEntryIndex < KOldAttachmentCount; ++oldEntryIndex) + { + CAgnAttachment& oldAttach = aOldEntry.Attachment(oldEntryIndex); + if (oldAttach.Type() == CCalContent::EDispositionInline && oldAttach.Uid() != 0) + { + CAgnAttachmentFile& oldAttachFile = static_cast(oldAttach); + TDriveName oldDrive = oldAttachFile.Drive(); + + for (TInt newEntryIndex = 0; newEntryIndex < KNewAttachmentCount; ++newEntryIndex) + { + CAgnAttachmentFile& newAttachFile = static_cast(aNewEntry.Attachment(newEntryIndex)); + if (newAttachFile.Uid() == oldAttach.Uid() && newAttachFile.Drive() != oldDrive) + { + HBufC* newfilename = oldAttachFile.FileName().AllocLC(); + newfilename->Des().Replace(0,2,newAttachFile.Drive()); + iAgnServerFile->MoveFileL(oldAttachFile.FileName(), newfilename->Des()); + CleanupStack::PopAndDestroy(newfilename); + + break; + } + } + } + } + } + +void CAgnEntryModel::MoveAttachmentToDriveL(CAgnAttachmentFile& aOldFileAttachment, CAgnAttachmentFile& aNewFileAttachment) + { + TParsePtrC parse(aOldFileAttachment.FileName()); + HBufC* fileName = GenerateFilenameLC(aNewFileAttachment.Drive(), parse.NameAndExt()); + TPtr pFilename(fileName->Des()); + iAgnServerFile->MoveFileL(aOldFileAttachment.FileName(), pFilename); + aNewFileAttachment.SetFileNameL(*fileName); + CleanupStack::PopAndDestroy(fileName); + + #if defined (__CAL_ATTACH_LOGGING__) || (__CAL_VERBOSE_LOGGING__) + HBufC8* attachId = aNewFileAttachment.ContentId().AllocLC(); + AgmDebug::DebugLog("Moving attachment to drive: Old filename: %S to New filename with drive: %S", &aOldFileAttachment.FileName(), &pFilename); + AgmDebug::DebugLog("Attachment: UId - %S, Old filename size - %d, New filename size - %d", attachId, aOldFileAttachment.Size(), aNewFileAttachment.Size()); + CleanupStack::PopAndDestroy(attachId); + #endif + } + +const CAgnSimpleEntry* CAgnEntryModel::GetSimpleEntryFromIndexes(const TAgnEntryId& aEntryId) + { + return iSimpleEntryTable->GetEntry(aEntryId); + } + +const CAgnSimpleEntry* CAgnEntryModel::GetSimpleEntryFromIndexes(TCalLocalUid aUniqueId) + { + return iSimpleEntryTable->GetEntry(aUniqueId); + } + +/** +Gets an entry based on its entry ID. + +@internalComponent +@capability ReadUserData +@param aId The entry ID of the entry to retrieve. +@return Pointer to the entry. +*/ +CAgnEntry* CAgnEntryModel::FetchEntryL(const TAgnEntryId& aId) const + { + CAgnEntry* entry = iEntryManager->FetchEntryL(aId); + if (entry) + { + CleanupStack::PushL(entry); + + if(iTzRuleIndex) + { + iTzRuleIndex->FetchTzRuleL(*entry); + } + + _DBGLOG_ENTRY(AgmDebug::DebugLog("FetchEntryL: Fetched entry with Stream Id %d, Partial Id %d",aId.Value(), aId.PartialId());) + + if ( entry->GsDataType() == CGsData::EChild ) + { + _DBGLOG_ENTRY(AgmDebug::DebugLog("FetchEntryL: Entry fetched is a child entry, Parent Id - %d", entry->ParentId());) + + // if a child entry has been fetched, get the recurrence ID and range stored with the parent + CAgnEntry* parent = FetchEntryL(entry->ParentId()); + + __ASSERT_ALWAYS(parent, User::Leave(KErrCorrupt)); // child without parent entry in DB! + CleanupStack::PushL(parent); + + entry->SetRecurrenceIdFromParentL(*parent); + + CleanupStack::PopAndDestroy(parent); + } + + _DBGLOG_ENTRY(AgmDebug::DebugLogEntryL(*entry, EDumpEntryAll);) + CleanupStack::Pop(entry); + } + return (entry); + } + +/** +Gets an entry based on its unique ID. + +@internalComponent +@capability ReadUserData +@param aId The unique ID of the entry to retrieve. +@return Pointer to the entry. +*/ +CAgnEntry* CAgnEntryModel::FetchEntryL(TCalLocalUid aUniqueId) const + { + _DBGLOG_ENTRY(AgmDebug::DebugLog("FetchEntryL: Attempting to fetch simple entry with LocalUid='%d'", aUniqueId);) + + // find entry in indexes to get the entry ID + CAgnSimpleEntry* simpleEntry = iSimpleEntryTable->GetEntry(aUniqueId); + CAgnEntry* entry = NULL; + + if ( simpleEntry ) + { + _DBGLOG_ENTRY(AgmDebug::DebugLog("FetchEntryL: Entry found, fetching the full entry with Local ID %d",simpleEntry->EntryId().Value());) + + // fetch the full entry from the entry ID + entry = FetchEntryL(simpleEntry->EntryId()); + } + #if defined (__CAL_ENTRY_LOGGING__) || (__CAL_VERBOSE_LOGGING__) + else + { + AgmDebug::DebugLog("FetchEntryL: Entry not found"); + } + #endif + + + return ( entry ); + } + +/** +Gets entries based on GUID. + +@internalComponent +@capability ReadUserData +@param aGuid The GUID of the entry to retrieve +@param aList The list of CAgnEntry objects +*/ +void CAgnEntryModel::FetchEntriesL(const TDesC8& aGuid, RPointerArray& aList) const + { + CAgnEntry* parentEntry = FetchEntryL(aGuid); + + #if defined (__CAL_ENTRY_LOGGING__) || (__CAL_VERBOSE_LOGGING__) + TBuf guidBuf; + guidBuf.Copy(aGuid); + AgmDebug::DebugLog("FetchEntriesL: Using GUID='%S'", &guidBuf); + #endif + + if ( parentEntry ) + { + #if defined (__CAL_ENTRY_LOGGING__) || (__CAL_VERBOSE_LOGGING__) + TBuf guidBuf; + guidBuf.Copy(aGuid); + AgmDebug::DebugLog("FetchEntriesL: Found a parent entry with GUID='%S'", &guidBuf); + #endif + + CleanupStack::PushL(parentEntry); + aList.AppendL(parentEntry); + CleanupStack::Pop(parentEntry); + + // Fetch the children and add them to the array + const RArray& KIds = parentEntry->ChildIds(); + + const TInt KCount = KIds.Count(); + + _DBGLOG_ENTRY(AgmDebug::DebugLog("FetchEntriesL: Parent entry has %d child(ren)", KCount);) + + for ( TInt i = 0; i < KCount; ++i ) + { + CAgnEntry* childEntry = FetchEntryL(KIds[i].ChildId()); // pass flag so as not to tell parent to update its child list + if (childEntry) + { + CleanupStack::PushL(childEntry); + aList.AppendL(childEntry); + CleanupStack::Pop(childEntry); + } + } + } // parentEntry + } + + +CAgnEntry* CAgnEntryModel::FetchEntryL(const TDesC8& aGuid, const TAgnCalendarTime& aRecurrenceId) const + { + CAgnEntry* returnEntry = NULL; + CAgnEntry* parentEntry = FetchEntryL(aGuid); + + if ( parentEntry ) + { + CleanupStack::PushL(parentEntry); + returnEntry = FindChildFromParentL(*parentEntry, aRecurrenceId); + CleanupStack::PopAndDestroy(parentEntry); + } + + return returnEntry; + } + +TBool CAgnEntryModel::AreIndexesBuilt() const + { + if (iAgnServerFile) + { + return iAgnServerFile->AreIndexesBuilt(); + } + return EFalse; + } + + +void CAgnEntryModel::RestoreCategoriesL() + { + if ( iCalConverter ) + { + iCalConverter->InternalizeCategoriesL(); + } + } + +void CAgnEntryModel::OpenAttachmentFileL(RFile& aFile, TInt aAttachmentUid) const + { + const CAgnAttachmentIndexItem* const item = iAttachmentIndex->Attachment(aAttachmentUid); + // item is owned by iAttachmentIndex + + if ( item ) + { + iAgnServerFile->OpenFileL(aFile, item->FileName()); + } + else + { + User::Leave(KErrNotFound); + } + } + + +void CAgnEntryModel::CreateNewFileL(RFile& aFile, const TDesC& aFileName) + { + iAgnServerFile->CreateNewFileL(aFile, aFileName); + } + +// Called when a binary data attachment is stored. +// At this stage, a new file has been created, and the file handle returned to the client. +// 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. +void CAgnEntryModel::UpdateAttachmentDetailsL(TCalLocalUid aLocalUid, TInt aAttachmentIndex, const TDesC& aFileName, TInt aAttachmentSize) + { + _DBGLOG_ATTACH(AgmDebug::DebugLog("UpdateAttachmentDetailsL: Local Uid %d, Attachment Index %d, FileName %S, Attachment Size %d", aLocalUid, aAttachmentIndex, &aFileName, aAttachmentSize);) + + CAgnEntry* entry = FetchEntryL(aLocalUid); + + if ( entry ) + { + CleanupStack::PushL(entry); + + CAgnAttachmentFile* attachment = static_cast(&entry->Attachment(aAttachmentIndex)); + + if ( attachment && attachment->FileName().Length() <= KMaxDriveName) + { + attachment->SetFileNameL(aFileName); + attachment->SetSize(aAttachmentSize); + attachment->SetUid(iNextAttachmentUid); + ++iNextAttachmentUid; + UpdateEntryL(*entry, NULL, EFalse); + + ExternalizeNextUidValuesL(); + } + + CleanupStack::PopAndDestroy(entry); + } + } + +TInt CAgnEntryModel::TransferFileFromClientL(RFile& aAttachfileHandle, CAgnAttachmentFile& aAttachFile, CAgnEntry& aEntry, TBool aIsSameDrive) + { + RBuf originalFileName; + originalFileName.CreateL(KMaxFileName); + CleanupClosePushL(originalFileName); + aAttachfileHandle.FullName(originalFileName); + TInt size; + User::LeaveIfError(aAttachfileHandle.Size(size)); + + // Generate attachment filename + + TParsePtrC parseOriginalFile(originalFileName); + HBufC* fileName = GenerateFilenameLC(aAttachFile.FileName(), parseOriginalFile.NameAndExt()); + TPtr attachFilename(fileName->Des()); + + _DBGLOG_ATTACH(AgmDebug::DebugLog("TransferFileFromClientL: Transferring file from: %S of size %d to %S", &originalFileName, size, &attachFilename);) + if(aIsSameDrive) + { + User::LeaveIfError(aAttachfileHandle.Rename(attachFilename)); // move the file to calendar area + } + else + { + aAttachfileHandle.Close(); + iAgnServerFile->MoveFileL(originalFileName, attachFilename); + } + aAttachFile.SetFileNameL(attachFilename); + aAttachFile.SetSize(size); + aAttachFile.SetUid(iNextAttachmentUid++); + + // Don't call UpdateEntryL as we only need to update the Calendar db file + TRAPD(err, UpdateEntryL(aEntry, NULL, EFalse)); + if (err != KErrNone) + { + // if the entry failed to update, move the attachment back + if (aIsSameDrive) + { + User::LeaveIfError(aAttachfileHandle.Rename(originalFileName)); + } + else + { + iAgnServerFile->MoveFileL(fileName->Des(), originalFileName); + } + User::Leave(err); + } + + CleanupStack::PopAndDestroy(2, &originalFileName); + ExternalizeNextUidValuesL(); + return iNextAttachmentUid-1; + } +TInt CAgnEntryModel::MoveFileToServerL(TCalLocalUid aLocalUid, TInt aAttachmentIndex) + { + TInt ret = 0; + TRAPD(err, ret = DoMoveFileToServerL(aLocalUid, aAttachmentIndex)); + if(err != KErrNone) + { + iAttachmentFileHandle.Close(); + User::Leave(err); + } + return ret; + } + +TInt CAgnEntryModel::DoMoveFileToServerL(TCalLocalUid aLocalUid, TInt aAttachmentIndex) + { + _DBGLOG_ATTACH(AgmDebug::DebugLog("DoMoveFileToServerL: Local Uid %d, Attachment Index %d", aLocalUid, aAttachmentIndex);) + + TCalAttachmentUid attachUid(0); + + CAgnEntry* entry = FetchEntryL(aLocalUid); + __ASSERT_ALWAYS(entry, User::Leave(KErrCorrupt)); + CleanupStack::PushL(entry); + + CAgnAttachment& attach = entry->Attachment(aAttachmentIndex); + __ASSERT_ALWAYS(attach.Type() == CCalContent::EDispositionInline, User::Leave(KErrCorrupt)); + + CAgnAttachmentFile& attachFile = static_cast(attach); + __ASSERT_ALWAYS(attachFile.Drive().CompareF(KDefaultAttachmentDrive()),User::Leave(KErrCorrupt) );//Drive is the default one + + attachUid = TransferFileFromClientL(iAttachmentFileHandle,attachFile, *entry, EFalse); + + CleanupStack::PopAndDestroy(entry); + return attachUid; + } + +TInt CAgnEntryModel::TransferAttachmentFileToServerL(RFile& aFile, TCalLocalUid aLocalUid, TInt aAttachmentIndex) + { + _DBGLOG_ATTACH(AgmDebug::DebugLog("TransferAttachmentFileToServerL: Local Uid %d, Attachment Index %d", aLocalUid, aAttachmentIndex);) + + TCalAttachmentUid attachUid(0); + CAgnEntry* entry = FetchEntryL(aLocalUid); + __ASSERT_ALWAYS(entry, User::Leave(KErrCorrupt)); + CleanupStack::PushL(entry); + + CAgnAttachment& attach = entry->Attachment(aAttachmentIndex); + __ASSERT_ALWAYS(attach.Type() == CCalContent::EDispositionInline, User::Leave(KErrCorrupt)); + + CAgnAttachmentFile& attachFile = static_cast(attach); + + if(!attachFile.Drive().CompareF(KDefaultAttachmentDrive()))//Drive is the default one + { + attachUid = TransferFileFromClientL(aFile, attachFile, *entry, ETrue); + } + else + {//Client need to close the handle in order to move the original file to the drive specified. + User::LeaveIfError(iAttachmentFileHandle.Duplicate(aFile)); + } + + CleanupStack::PopAndDestroy(entry); + return attachUid; + } + +// Generate a filename for an attachment on the specified drive. +HBufC* CAgnEntryModel::GenerateFilenameLC(const TDesC& aDrive, const TDesC& aFileName) + { + _LIT(KCalDirectory, "\\"); + + // file name is "X:\\private\\10003a5b\\calendarfilename_a\\Y\\filename" + // where X is the drive specified (KDefaultAttachmentDrive if none is set) + // and Y is the folder number calculated from the attachment ID + const TInt KNumberOfAttachmentsPerFolder = 32; + //Restricting the Attachemnt folder name length to 2 chars, where it allows to add max0-99 folders + //by considering the KMaxFileName Length is allowed 220 Chars + const TInt KMaxNumOfAttachmentFolders = 100; + // 8 to cover attachment folder name and trailing number if there exists a same file name + const TInt KExtraFileNameLength = 8; + TInt fileNameLength = iAgnServerFile->FileName().Length() + KExtraFileNameLength + aFileName.Length(); + TPtrC fileNamePtr (aFileName); + if(fileNameLength > KMaxFileName) + { + const TInt KMinFileNameLength = 8; + fileNameLength = iAgnServerFile->FileName().Length() + KExtraFileNameLength + KMinFileNameLength; + if(fileNameLength > KMaxFileName) + { + User::Leave(KErrBadName); + } + else + { + fileNamePtr.Set(aFileName.Left(KMinFileNameLength - 2));//save 2 for trailling number + } + } + HBufC* fileName = HBufC::NewLC(fileNameLength); + TPtr folderNamePtr = fileName->Des(); + iAgnServerFile->GetAttachmentFolderNameL(folderNamePtr); + + // if the drive has been set already, set it on the filename + if ( aDrive.Length() >= 1 ) + { + folderNamePtr.Replace(0, 1, aDrive.Left(1)); + } + else + { + folderNamePtr.Replace(0, 1, KDefaultAttachmentDrive().Left(1)); + } + + const TInt KFolderNumber = iNextAttachmentUid / KNumberOfAttachmentsPerFolder; + if (KFolderNumber >= KMaxNumOfAttachmentFolders) + { + User::LeaveIfError(KErrDirFull); + } + folderNamePtr.AppendNum(KFolderNumber); + folderNamePtr.Append(KCalDirectory); + TPtr fullFileNamePtr(folderNamePtr); + fullFileNamePtr.Append(fileNamePtr); + TBool uniqueFilenameGenerated = EFalse; + TInt count = 0; + while ( ! uniqueFilenameGenerated ) + { + if ( !iAgnServerFile->FileExistsL(fullFileNamePtr) ) + { + uniqueFilenameGenerated = ETrue; + } + else + { + TParsePtrC parse(fileNamePtr); + fullFileNamePtr = folderNamePtr; + fullFileNamePtr.Append(parse.Name()); + fullFileNamePtr.AppendNum(count++); + fullFileNamePtr.Append(parse.Ext()); + } + } + + iAgnServerFile->CreateDirL(fullFileNamePtr); + return fileName; + } + +// Generate a filename for an attachment on the specified drive. +HBufC* CAgnEntryModel::GenerateRandomFilenameLC(const TDesC& aDrive) + { + _LIT(KCalDirectory, "\\"); + + // file name is "X:\\private\\10003a5b\\calendarfilename_a\\Y\\filename" + // where X is the drive specified (KDefaultAttachmentDrive if none is set) + // and Y is the folder number calculated from the attachment ID + const TInt KNumberOfAttachmentsPerFolder = 32; + const TInt KNumCharsInFileName = 8; + //Restricting the Attachemnt folder name length to 2 chars, where it allows to add max0-99 folders + //by considering the KMaxFileName Length is allowed 220 Chars + const TInt KMaxNumOfAttachmentFolders = 100; + + // 16 to cover attachment folder name and extension - could be 'foldername\\888\\filename.xxx' + const TInt KFileNameLength = iAgnServerFile->FileName().Length() + 32 + KNumCharsInFileName; + + HBufC* fileName = HBufC::NewLC(KFileNameLength); + TPtr fileNamePtr = fileName->Des(); + + iAgnServerFile->GetAttachmentFolderNameL(fileNamePtr); + + // if the drive has been set already, set it on the filename + if ( aDrive.Length() >= 1 ) + { + fileNamePtr.Replace(0, 1, aDrive.Left(1)); + } + else + { + fileNamePtr.Replace(0, 1, KDefaultAttachmentDrive().Left(1)); + } + + const TInt KFolderNumber = iNextAttachmentUid / KNumberOfAttachmentsPerFolder; + if (KFolderNumber >= KMaxNumOfAttachmentFolders) + { + User::LeaveIfError(KErrDirFull); + } + fileNamePtr.AppendNum(KFolderNumber); + fileNamePtr.Append(KCalDirectory); + + TBool uniqueFilenameGenerated = EFalse; + + TTime time; + time.UniversalTime(); + TInt64 seed = time.Int64() + iNextAttachmentUid; + + while ( ! uniqueFilenameGenerated ) + { + // Generate a random filename + for ( TInt i = 0; i < KNumCharsInFileName; ++i, ++seed ) + { + TChar randomChar = (Math::Rand(seed) % 26) + 'a'; + fileNamePtr.Append(randomChar); + } + + // check that there is not an existing filename with the same name (very unlikely!) + if ( iAgnServerFile->FileExistsL(fileNamePtr) ) + { + // Remove this file name from the descriptor if the file exists already, and generate a new name + fileNamePtr.SetLength(fileNamePtr.Length() - KNumCharsInFileName); + } + else + { + uniqueFilenameGenerated = ETrue; + } + } + + iAgnServerFile->CreateDirL(fileNamePtr); + return fileName; + } + +const RArray* CAgnEntryModel::GetEntriesWithAttachment(TCalAttachmentUid aAttachmentUid) const + { + _DBGLOG_ATTACH(AgmDebug::DebugLog("GetEntriesWithAttachment: Attachment Uid %d", aAttachmentUid);) + + const CAgnAttachmentIndexItem* const item = iAttachmentIndex->Attachment(aAttachmentUid); + + if ( item && item->Entries().Count()) + { + return &item->Entries(); + } + + return NULL; + } + + +void CAgnEntryModel::GetSortedAttachmentsL(RArray& aAttachmentIds, CCalAttachmentManager::TSortOrder aSortType) + { + RPointerArray sortedAttachments; + CleanupClosePushL(sortedAttachments); + iAttachmentIndex->GetSortedIndexL(aSortType, sortedAttachments); + + const TInt KAttachmentCount = sortedAttachments.Count(); + for ( TInt i = 0; i < KAttachmentCount; ++i) + { + aAttachmentIds.AppendL(sortedAttachments[i]->Uid()); + } + + CleanupStack::PopAndDestroy(&sortedAttachments); + } + + +CAgnAttachment* CAgnEntryModel::FetchAttachmentByIdL(TCalAttachmentUid aAttachUid) + { + _DBGLOG_ATTACH(AgmDebug::DebugLog("FetchAttachmentByIdL: Attachment Uid %d", aAttachUid);) + + const CAgnAttachmentIndexItem* const item = iAttachmentIndex->Attachment(aAttachUid); + CAgnAttachment* attachmentToWrite = NULL; + + if ( item ) + { + const RArray& KEntries = item->Entries(); + + if ( KEntries.Count() == 0 ) + { + // corrupt index - remove the attachment and return NULL + iAttachmentIndex->RemoveAttachment(aAttachUid); + } + else + { + const TCalLocalUid& Kid = KEntries[0]; + CAgnEntry* entry = FetchEntryL(Kid); + + if ( entry ) + { + CleanupStack::PushL(entry); + + for ( TInt i = 0; i < entry->AttachmentCount(); ++i ) + { + if ( entry->Attachment(i).Uid() == aAttachUid ) + { + attachmentToWrite = AttachmentFactory::CloneL(entry->Attachment(i)); + + break; + } + } + + CleanupStack::PopAndDestroy(entry); + } + } + } + + return attachmentToWrite; + } + +const TDesC8& CAgnEntryModel::GetEntryGuidL(CAgnEntry& aEntry) const + { + if(aEntry.Guid() == KNullDesC8) + {//it is a child and its uid has not been loaded + CAgnEntry* parentEntry = FetchEntryL(aEntry.ParentId()); + __ASSERT_DEBUG(parentEntry, Panic(EAgmErrChildWithoutParent)); + if(parentEntry) + { + CleanupStack::PushL(parentEntry); + HBufC8* guidHbuf = parentEntry->Guid().AllocL(); + CleanupStack::PopAndDestroy(parentEntry); + aEntry.SetGuid(guidHbuf); + } + } + return aEntry.Guid(); + } + + +/** +Returns A reference to the stream store in which the model's data is stored. +@internalComponent +*/ +CStreamStore& CAgnEntryModel::StreamStore() const + { + return iEntryManager->StreamStore(); + } + +CCalAsyncDelete* CAgnEntryModel::CreateAsyncDeleteL(TAgnChangeFilter& aChangeFilter) + { + return CCalAsyncDelete::NewL(*this, aChangeFilter, *iSimpleEntryTable); + } + +void CAgnEntryModel::LoadNewStreamStoreL(CStreamStore& aStore, const TStreamId& aModelStreamId, CAgnEntryManager& aEntryManager, CAgnTzRuleIndex& aTzRuleIndex) + { + iEntryManager->SetStore(aStore); + + iEntryManager->CopyStreamIds(aEntryManager); + iModelStreamIdSet->LoadL(aStore, aModelStreamId); + + if(iTzRuleIndex) + { + delete iTzRuleIndex; + } + + iTzRuleIndex = &aTzRuleIndex; + iTzRuleIndex->CheckTzDbModificationL(*iAgnServerFile); + } + +TBool CAgnEntryModel::StreamsAreEmpty() const + { + return (iModelStreamIdSet->EntryStreamIdSet().Count() == 0); + } + +TAgnEntryIter* CAgnEntryModel::CreateEntryIterL() const + { + return new (ELeave) TAgnEntryIter(iModelStreamIdSet->EntryStreamIdSet(), *iEntryManager); + } + +CAgnCategoryIndex& CAgnEntryModel::CategoryIndex() const +/** Gets the category index. + +@return The agenda model category index object. */ + { + return ( *iCategoryIndex ); + } + +TTime CAgnEntryModel::TzRulesLastModifiedDateL() + { + return iTzRuleIndex->TzRulesLastModifiedDateL(); + } + +void CAgnEntryModel::HandleTzRulesChangeL(const TTime& aTime) + { + iTzRuleIndex->HandleTzRulesChangeL(aTime); + } + +CAgnAlarm& CAgnEntryModel::Alarm() + { + return *iAlarm; + }