/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsILDAPMessage.h" #include "nsAbLDAPReplicationData.h" #include "nsIAbCard.h" #include "nsAbBaseCID.h" #include "nsAbUtils.h" #include "nsAbLDAPReplicationQuery.h" #include "nsILDAPErrors.h" #include "nsComponentManagerUtils.h" #include "nsMsgUtils.h" // once bug # 101252 gets fixed, this should be reverted back to be non threadsafe // implementation is not really thread safe since each object should exist // independently along with its related independent nsAbLDAPReplicationQuery object. NS_IMPL_ISUPPORTS(nsAbLDAPProcessReplicationData, nsIAbLDAPProcessReplicationData, nsILDAPMessageListener) nsAbLDAPProcessReplicationData::nsAbLDAPProcessReplicationData() : nsAbLDAPListenerBase(), mState(kIdle), mProtocol(-1), mCount(0), mDBOpen(false), mInitialized(false) { } nsAbLDAPProcessReplicationData::~nsAbLDAPProcessReplicationData() { /* destructor code */ if(mDBOpen && mReplicationDB) mReplicationDB->Close(false); } NS_IMETHODIMP nsAbLDAPProcessReplicationData::Init( nsIAbLDAPDirectory *aDirectory, nsILDAPConnection *aConnection, nsILDAPURL* aURL, nsIAbLDAPReplicationQuery *aQuery, nsIWebProgressListener *aProgressListener) { NS_ENSURE_ARG_POINTER(aDirectory); NS_ENSURE_ARG_POINTER(aConnection); NS_ENSURE_ARG_POINTER(aURL); NS_ENSURE_ARG_POINTER(aQuery); mDirectory = aDirectory; mConnection = aConnection; mDirectoryUrl = aURL; mQuery = aQuery; mListener = aProgressListener; nsresult rv = mDirectory->GetAttributeMap(getter_AddRefs(mAttrMap)); if (NS_FAILED(rv)) { mQuery = nullptr; return rv; } rv = mDirectory->GetAuthDn(mLogin); if (NS_FAILED(rv)) { mQuery = nullptr; return rv; } rv = mDirectory->GetSaslMechanism(mSaslMechanism); if (NS_FAILED(rv)) { mQuery = nullptr; return rv; } mInitialized = true; return rv; } NS_IMETHODIMP nsAbLDAPProcessReplicationData::GetReplicationState(int32_t *aReplicationState) { NS_ENSURE_ARG_POINTER(aReplicationState); *aReplicationState = mState; return NS_OK; } NS_IMETHODIMP nsAbLDAPProcessReplicationData::GetProtocolUsed(int32_t *aProtocolUsed) { NS_ENSURE_ARG_POINTER(aProtocolUsed); *aProtocolUsed = mProtocol; return NS_OK; } NS_IMETHODIMP nsAbLDAPProcessReplicationData::OnLDAPMessage(nsILDAPMessage *aMessage) { NS_ENSURE_ARG_POINTER(aMessage); if (!mInitialized) return NS_ERROR_NOT_INITIALIZED; int32_t messageType; nsresult rv = aMessage->GetType(&messageType); if (NS_FAILED(rv)) { Done(false); return rv; } switch (messageType) { case nsILDAPMessage::RES_BIND: rv = OnLDAPMessageBind(aMessage); if (NS_FAILED(rv)) rv = Abort(); break; case nsILDAPMessage::RES_SEARCH_ENTRY: rv = OnLDAPSearchEntry(aMessage); break; case nsILDAPMessage::RES_SEARCH_RESULT: rv = OnLDAPSearchResult(aMessage); break; default: // for messageTypes we do not handle return NS_OK to LDAP and move ahead. rv = NS_OK; break; } return rv; } NS_IMETHODIMP nsAbLDAPProcessReplicationData::Abort() { if (!mInitialized) return NS_ERROR_NOT_INITIALIZED; nsresult rv = NS_OK; if (mState != kIdle && mOperation) { rv = mOperation->AbandonExt(); if (NS_SUCCEEDED(rv)) mState = kIdle; } if (mReplicationDB && mDBOpen) { // force close since we need to delete the file. mReplicationDB->ForceClosed(); mDBOpen = false; // delete the unsaved replication file if (mReplicationFile) { rv = mReplicationFile->Remove(false); if (NS_SUCCEEDED(rv) && mDirectory) { nsAutoCString fileName; rv = mDirectory->GetReplicationFileName(fileName); // now put back the backed up replicated file if aborted if (NS_SUCCEEDED(rv) && mBackupReplicationFile) rv = mBackupReplicationFile->MoveToNative(nullptr, fileName); } } } Done(false); return rv; } nsresult nsAbLDAPProcessReplicationData::DoTask() { if (!mInitialized) return NS_ERROR_NOT_INITIALIZED; nsresult rv = OpenABForReplicatedDir(true); if (NS_FAILED(rv)) // do not call done here since it is called by OpenABForReplicationDir return rv; mOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = mOperation->Init(mConnection, this, nullptr); NS_ENSURE_SUCCESS(rv, rv); // get the relevant attributes associated with the directory server url nsAutoCString urlFilter; rv = mDirectoryUrl->GetFilter(urlFilter); if (NS_FAILED(rv)) return rv; nsAutoCString dn; rv = mDirectoryUrl->GetDn(dn); if (NS_FAILED(rv)) return rv; if (dn.IsEmpty()) return NS_ERROR_UNEXPECTED; int32_t scope; rv = mDirectoryUrl->GetScope(&scope); if (NS_FAILED(rv)) return rv; nsAutoCString attributes; rv = mDirectoryUrl->GetAttributes(attributes); if (NS_FAILED(rv)) return rv; mState = kReplicatingAll; if (mListener && NS_SUCCEEDED(rv)) // XXX Cast from bool to nsresult mListener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_START, static_cast(true)); return mOperation->SearchExt(dn, scope, urlFilter, attributes, 0, 0); } void nsAbLDAPProcessReplicationData::InitFailed(bool aCancelled) { // Just call Done() which will ensure everything is tidied up nicely. Done(false); } nsresult nsAbLDAPProcessReplicationData::OnLDAPSearchEntry(nsILDAPMessage *aMessage) { NS_ENSURE_ARG_POINTER(aMessage); if (!mInitialized) return NS_ERROR_NOT_INITIALIZED; // since this runs on the main thread and is single threaded, this will // take care of entries returned by LDAP Connection thread after Abort. if (!mReplicationDB || !mDBOpen) return NS_ERROR_FAILURE; nsresult rv = NS_OK; // Although we would may naturally create an nsIAbLDAPCard here, we don't // need to as we are writing this straight to the database, so just create // the database version instead. nsCOMPtr newCard(do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv)); if (NS_FAILED(rv)) { Abort(); return rv; } rv = mAttrMap->SetCardPropertiesFromLDAPMessage(aMessage, newCard); if (NS_FAILED(rv)) { NS_WARNING("nsAbLDAPProcessReplicationData::OnLDAPSearchEntry" "No card properties could be set"); // if some entries are bogus for us, continue with next one return NS_OK; } rv = mReplicationDB->CreateNewCardAndAddToDB(newCard, false, nullptr); if(NS_FAILED(rv)) { Abort(); return rv; } // now set the attribute for the DN of the entry in the card in the DB nsAutoCString authDN; rv = aMessage->GetDn(authDN); if(NS_SUCCEEDED(rv) && !authDN.IsEmpty()) { newCard->SetPropertyAsAUTF8String("_DN", authDN); } rv = mReplicationDB->EditCard(newCard, false, nullptr); if(NS_FAILED(rv)) { Abort(); return rv; } mCount ++; if (mListener && !(mCount % 10)) // inform the listener every 10 entries { mListener->OnProgressChange(nullptr,nullptr,mCount, -1, mCount, -1); // in case if the LDAP Connection thread is starved and causes problem // uncomment this one and try. // PR_Sleep(PR_INTERVAL_NO_WAIT); // give others a chance } return rv; } nsresult nsAbLDAPProcessReplicationData::OnLDAPSearchResult(nsILDAPMessage *aMessage) { #ifdef DEBUG_rdayal printf("LDAP Replication : Got Results for Completion"); #endif NS_ENSURE_ARG_POINTER(aMessage); if(!mInitialized) return NS_ERROR_NOT_INITIALIZED; int32_t errorCode; nsresult rv = aMessage->GetErrorCode(&errorCode); if(NS_SUCCEEDED(rv)) { // We are done with the LDAP search for all entries. if(errorCode == nsILDAPErrors::SUCCESS || errorCode == nsILDAPErrors::SIZELIMIT_EXCEEDED) { Done(true); if(mReplicationDB && mDBOpen) { rv = mReplicationDB->Close(true); NS_ASSERTION(NS_SUCCEEDED(rv), "Replication DB Close on Success failed"); mDBOpen = false; // once we have saved the new replication file, delete the backup file if(mBackupReplicationFile) { rv = mBackupReplicationFile->Remove(false); NS_ASSERTION(NS_SUCCEEDED(rv), "Replication BackupFile Remove on Success failed"); } } return NS_OK; } } // in case if GetErrorCode returned error or errorCode is not SUCCESS / SIZELIMIT_EXCEEDED if(mReplicationDB && mDBOpen) { // if error result is returned close the DB without saving ??? // should we commit anyway ??? whatever is returned is not lost then !! rv = mReplicationDB->ForceClosed(); // force close since we need to delete the file. NS_ASSERTION(NS_SUCCEEDED(rv), "Replication DB ForceClosed on Failure failed"); mDBOpen = false; // if error result is returned remove the replicated file if(mReplicationFile) { rv = mReplicationFile->Remove(false); NS_ASSERTION(NS_SUCCEEDED(rv), "Replication File Remove on Failure failed"); if(NS_SUCCEEDED(rv)) { // now put back the backed up replicated file if(mBackupReplicationFile && mDirectory) { nsAutoCString fileName; rv = mDirectory->GetReplicationFileName(fileName); if (NS_SUCCEEDED(rv) && !fileName.IsEmpty()) { rv = mBackupReplicationFile->MoveToNative(nullptr, fileName); NS_ASSERTION(NS_SUCCEEDED(rv), "Replication Backup File Move back on Failure failed"); } } } } Done(false); } return NS_OK; } nsresult nsAbLDAPProcessReplicationData::OpenABForReplicatedDir(bool aCreate) { if (!mInitialized) return NS_ERROR_NOT_INITIALIZED; nsresult rv = mDirectory->GetReplicationFile(getter_AddRefs(mReplicationFile)); if (NS_FAILED(rv)) { Done(false); return NS_ERROR_FAILURE; } nsCString fileName; rv = mReplicationFile->GetNativeLeafName(fileName); if (NS_FAILED(rv)) { Done(false); return rv; } // if the AB DB already exists backup existing one, // in case if the user cancels or Abort put back the backed up file bool fileExists; rv = mReplicationFile->Exists(&fileExists); if(NS_SUCCEEDED(rv) && fileExists) { // create the backup file object same as the Replication file object. // we create a backup file here since we need to cleanup the existing file // for create and then commit so instead of deleting existing cards we just // clone the existing one for a much better performance - for Download All. // And also important in case if replication fails we donot lose user's existing // replicated data for both Download all and Changelog. nsCOMPtr clone; rv = mReplicationFile->Clone(getter_AddRefs(clone)); if(NS_FAILED(rv)) { Done(false); return rv; } mBackupReplicationFile = do_QueryInterface(clone, &rv); if(NS_FAILED(rv)) { Done(false); return rv; } rv = mBackupReplicationFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0777); if(NS_FAILED(rv)) { Done(false); return rv; } nsAutoString backupFileLeafName; rv = mBackupReplicationFile->GetLeafName(backupFileLeafName); if(NS_FAILED(rv)) { Done(false); return rv; } // remove the newly created unique backup file so that move and copy succeeds. rv = mBackupReplicationFile->Remove(false); if(NS_FAILED(rv)) { Done(false); return rv; } if(aCreate) { // set backup file to existing replication file for move mBackupReplicationFile->SetNativeLeafName(fileName); rv = mBackupReplicationFile->MoveTo(nullptr, backupFileLeafName); // set the backup file leaf name now if (NS_SUCCEEDED(rv)) mBackupReplicationFile->SetLeafName(backupFileLeafName); } else { // set backup file to existing replication file for copy mBackupReplicationFile->SetNativeLeafName(fileName); // specify the parent here specifically, // passing nullptr to copy to the same dir actually renames existing file // instead of making another copy of the existing file. nsCOMPtr parent; rv = mBackupReplicationFile->GetParent(getter_AddRefs(parent)); if (NS_SUCCEEDED(rv)) rv = mBackupReplicationFile->CopyTo(parent, backupFileLeafName); // set the backup file leaf name now if (NS_SUCCEEDED(rv)) mBackupReplicationFile->SetLeafName(backupFileLeafName); } if(NS_FAILED(rv)) { Done(false); return rv; } } nsCOMPtr addrDBFactory = do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv); if(NS_FAILED(rv)) { if (mBackupReplicationFile) mBackupReplicationFile->Remove(false); Done(false); return rv; } rv = addrDBFactory->Open(mReplicationFile, aCreate, true, getter_AddRefs(mReplicationDB)); if(NS_FAILED(rv)) { Done(false); if (mBackupReplicationFile) mBackupReplicationFile->Remove(false); return rv; } mDBOpen = true; // replication DB is now Open return rv; } void nsAbLDAPProcessReplicationData::Done(bool aSuccess) { if (!mInitialized) return; mState = kReplicationDone; if (mQuery) mQuery->Done(aSuccess); if (mListener) // XXX Cast from bool to nsresult mListener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, static_cast(aSuccess)); // since this is called when all is done here, either on success, // failure or abort release the query now. mQuery = nullptr; } nsresult nsAbLDAPProcessReplicationData::DeleteCard(nsString & aDn) { nsCOMPtr cardToDelete; mReplicationDB->GetCardFromAttribute(nullptr, "_DN", NS_ConvertUTF16toUTF8(aDn), false, getter_AddRefs(cardToDelete)); return mReplicationDB->DeleteCard(cardToDelete, false, nullptr); }