/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* 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 "nsSubscribableServer.h" #include "nsIMsgIncomingServer.h" #include "prmem.h" #include "rdf.h" #include "nsRDFCID.h" #include "nsIServiceManager.h" #include "nsMsgI18N.h" #include "nsMsgUtils.h" #include "nsCOMArray.h" #include "nsArrayEnumerator.h" #include "nsServiceManagerUtils.h" static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); nsSubscribableServer::nsSubscribableServer(void) { mDelimiter = '.'; mShowFullName = true; mTreeRoot = nullptr; mStopped = false; } nsresult nsSubscribableServer::Init() { nsresult rv; rv = EnsureRDFService(); NS_ENSURE_SUCCESS(rv,rv); rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "child"), getter_AddRefs(kNC_Child)); NS_ENSURE_SUCCESS(rv,rv); rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Subscribed"), getter_AddRefs(kNC_Subscribed)); NS_ENSURE_SUCCESS(rv,rv); rv = mRDFService->GetLiteral(u"true", getter_AddRefs(kTrueLiteral)); NS_ENSURE_SUCCESS(rv,rv); rv = mRDFService->GetLiteral(u"false", getter_AddRefs(kFalseLiteral)); NS_ENSURE_SUCCESS(rv,rv); return NS_OK; } nsSubscribableServer::~nsSubscribableServer(void) { mozilla::DebugOnly rv = FreeSubtree(mTreeRoot); NS_ASSERTION(NS_SUCCEEDED(rv),"failed to free tree"); } NS_IMPL_ISUPPORTS(nsSubscribableServer, nsISubscribableServer) NS_IMETHODIMP nsSubscribableServer::SetIncomingServer(nsIMsgIncomingServer *aServer) { if (!aServer) { mIncomingServerUri.AssignLiteral(""); return NS_OK; } // We intentionally do not store a pointer to the aServer here // as it would create reference loops, because nsIImapIncomingServer // and nsINntpIncomingServer keep a reference to an internal // nsISubscribableServer object. // We only need the URI of the server anyway. return aServer->GetServerURI(mIncomingServerUri); } NS_IMETHODIMP nsSubscribableServer::GetDelimiter(char *aDelimiter) { if (!aDelimiter) return NS_ERROR_NULL_POINTER; *aDelimiter = mDelimiter; return NS_OK; } NS_IMETHODIMP nsSubscribableServer::SetDelimiter(char aDelimiter) { mDelimiter = aDelimiter; return NS_OK; } NS_IMETHODIMP nsSubscribableServer::SetAsSubscribed(const nsACString &path) { nsresult rv = NS_OK; SubscribeTreeNode *node = nullptr; rv = FindAndCreateNode(path, &node); NS_ENSURE_SUCCESS(rv,rv); NS_ASSERTION(node,"didn't find the node"); if (!node) return NS_ERROR_FAILURE; node->isSubscribable = true; node->isSubscribed = true; rv = NotifyChange(node, kNC_Subscribed, node->isSubscribed); NS_ENSURE_SUCCESS(rv,rv); return rv; } NS_IMETHODIMP nsSubscribableServer::AddTo(const nsACString& aName, bool aAddAsSubscribed, bool aSubscribable, bool aChangeIfExists) { nsresult rv = NS_OK; if (mStopped) { #ifdef DEBUG_seth printf("stopped!\n"); #endif return NS_ERROR_FAILURE; } SubscribeTreeNode *node = nullptr; // todo, shouldn't we pass in aAddAsSubscribed, for the // default value if we create it? rv = FindAndCreateNode(aName, &node); NS_ENSURE_SUCCESS(rv,rv); NS_ASSERTION(node,"didn't find the node"); if (!node) return NS_ERROR_FAILURE; if (aChangeIfExists) { node->isSubscribed = aAddAsSubscribed; rv = NotifyChange(node, kNC_Subscribed, node->isSubscribed); NS_ENSURE_SUCCESS(rv,rv); } node->isSubscribable = aSubscribable; return rv; } NS_IMETHODIMP nsSubscribableServer::SetState(const nsACString &aPath, bool aState, bool *aStateChanged) { nsresult rv = NS_OK; NS_ASSERTION(!aPath.IsEmpty() && aStateChanged, "no path or stateChanged"); if (aPath.IsEmpty() || !aStateChanged) return NS_ERROR_NULL_POINTER; NS_ASSERTION(MsgIsUTF8(aPath), "aPath is not in UTF-8"); *aStateChanged = false; SubscribeTreeNode *node = nullptr; rv = FindAndCreateNode(aPath, &node); NS_ENSURE_SUCCESS(rv,rv); NS_ASSERTION(node,"didn't find the node"); if (!node) return NS_ERROR_FAILURE; NS_ASSERTION(node->isSubscribable, "fix this"); if (!node->isSubscribable) { return NS_OK; } if (node->isSubscribed == aState) { return NS_OK; } else { node->isSubscribed = aState; *aStateChanged = true; rv = NotifyChange(node, kNC_Subscribed, node->isSubscribed); NS_ENSURE_SUCCESS(rv,rv); } return rv; } void nsSubscribableServer::BuildURIFromNode(SubscribeTreeNode *node, nsACString &uri) { if (node->parent) { BuildURIFromNode(node->parent, uri); if (node->parent == mTreeRoot) { uri += "/"; } else { uri += mDelimiter; } } uri += node->name; return; } nsresult nsSubscribableServer::NotifyAssert(SubscribeTreeNode *subjectNode, nsIRDFResource *property, SubscribeTreeNode *objectNode) { nsresult rv; bool hasObservers = true; rv = EnsureSubscribeDS(); NS_ENSURE_SUCCESS(rv,rv); rv = mSubscribeDS->GetHasObservers(&hasObservers); NS_ENSURE_SUCCESS(rv,rv); // no need to do all this work, there are no observers if (!hasObservers) { return NS_OK; } nsAutoCString subjectUri; BuildURIFromNode(subjectNode, subjectUri); // we could optimize this, since we know that objectUri == subjectUri + mDelimiter + object->name // is it worth it? nsAutoCString objectUri; BuildURIFromNode(objectNode, objectUri); nsCOMPtr subject; nsCOMPtr object; rv = EnsureRDFService(); NS_ENSURE_SUCCESS(rv,rv); rv = mRDFService->GetResource(subjectUri, getter_AddRefs(subject)); NS_ENSURE_SUCCESS(rv,rv); rv = mRDFService->GetResource(objectUri, getter_AddRefs(object)); NS_ENSURE_SUCCESS(rv,rv); rv = Notify(subject, property, object, true, false); NS_ENSURE_SUCCESS(rv,rv); return NS_OK; } nsresult nsSubscribableServer::EnsureRDFService() { nsresult rv; if (!mRDFService) { mRDFService = do_GetService(kRDFServiceCID, &rv); NS_ASSERTION(NS_SUCCEEDED(rv) && mRDFService, "failed to get rdf service"); NS_ENSURE_SUCCESS(rv,rv); if (!mRDFService) return NS_ERROR_FAILURE; } return NS_OK; } nsresult nsSubscribableServer::NotifyChange(SubscribeTreeNode *subjectNode, nsIRDFResource *property, bool value) { nsresult rv; nsCOMPtr subject; bool hasObservers = true; rv = EnsureSubscribeDS(); NS_ENSURE_SUCCESS(rv,rv); rv = mSubscribeDS->GetHasObservers(&hasObservers); NS_ENSURE_SUCCESS(rv,rv); // no need to do all this work, there are no observers if (!hasObservers) { return NS_OK; } nsAutoCString subjectUri; BuildURIFromNode(subjectNode, subjectUri); rv = EnsureRDFService(); NS_ENSURE_SUCCESS(rv,rv); rv = mRDFService->GetResource(subjectUri, getter_AddRefs(subject)); NS_ENSURE_SUCCESS(rv,rv); if (value) { rv = Notify(subject,property,kTrueLiteral,false,true); } else { rv = Notify(subject,property,kFalseLiteral,false,true); } NS_ENSURE_SUCCESS(rv,rv); return NS_OK; } nsresult nsSubscribableServer::EnsureSubscribeDS() { nsresult rv = NS_OK; if (!mSubscribeDS) { nsCOMPtr ds; rv = EnsureRDFService(); NS_ENSURE_SUCCESS(rv,rv); rv = mRDFService->GetDataSource("rdf:subscribe", getter_AddRefs(ds)); NS_ENSURE_SUCCESS(rv,rv); if (!ds) return NS_ERROR_FAILURE; mSubscribeDS = do_QueryInterface(ds, &rv); NS_ENSURE_SUCCESS(rv,rv); if (!mSubscribeDS) return NS_ERROR_FAILURE; } return NS_OK; } nsresult nsSubscribableServer::Notify(nsIRDFResource *subject, nsIRDFResource *property, nsIRDFNode *object, bool isAssert, bool isChange) { nsresult rv = NS_OK; rv = EnsureSubscribeDS(); NS_ENSURE_SUCCESS(rv,rv); rv = mSubscribeDS->NotifyObservers(subject, property, object, isAssert, isChange); NS_ENSURE_SUCCESS(rv,rv); return rv; } NS_IMETHODIMP nsSubscribableServer::SetSubscribeListener(nsISubscribeListener *aListener) { mSubscribeListener = aListener; return NS_OK; } NS_IMETHODIMP nsSubscribableServer::GetSubscribeListener(nsISubscribeListener **aListener) { if (!aListener) return NS_ERROR_NULL_POINTER; if (mSubscribeListener) { *aListener = mSubscribeListener; NS_ADDREF(*aListener); } return NS_OK; } NS_IMETHODIMP nsSubscribableServer::SubscribeCleanup() { NS_ASSERTION(false,"override this."); return NS_ERROR_FAILURE; } NS_IMETHODIMP nsSubscribableServer::StartPopulatingWithUri(nsIMsgWindow *aMsgWindow, bool aForceToServer, const char *uri) { mStopped = false; return NS_OK; } NS_IMETHODIMP nsSubscribableServer::StartPopulating(nsIMsgWindow *aMsgWindow, bool aForceToServer, bool aGetOnlyNew /*ignored*/) { nsresult rv = NS_OK; mStopped = false; rv = FreeSubtree(mTreeRoot); mTreeRoot = nullptr; NS_ENSURE_SUCCESS(rv,rv); return NS_OK; } NS_IMETHODIMP nsSubscribableServer::StopPopulating(nsIMsgWindow *aMsgWindow) { mStopped = true; return NS_OK; } NS_IMETHODIMP nsSubscribableServer::UpdateSubscribed() { NS_ASSERTION(false,"override this."); return NS_ERROR_FAILURE; } NS_IMETHODIMP nsSubscribableServer::Subscribe(const char16_t *aName) { NS_ASSERTION(false,"override this."); return NS_ERROR_FAILURE; } NS_IMETHODIMP nsSubscribableServer::Unsubscribe(const char16_t *aName) { NS_ASSERTION(false,"override this."); return NS_ERROR_FAILURE; } NS_IMETHODIMP nsSubscribableServer::SetShowFullName(bool showFullName) { mShowFullName = showFullName; return NS_OK; } nsresult nsSubscribableServer::FreeSubtree(SubscribeTreeNode *node) { nsresult rv = NS_OK; if (node) { // recursively free the children if (node->firstChild) { // will free node->firstChild rv = FreeSubtree(node->firstChild); NS_ENSURE_SUCCESS(rv,rv); node->firstChild = nullptr; } // recursively free the siblings if (node->nextSibling) { // will free node->nextSibling rv = FreeSubtree(node->nextSibling); NS_ENSURE_SUCCESS(rv,rv); node->nextSibling = nullptr; } #ifdef HAVE_SUBSCRIBE_DESCRIPTION NS_ASSERTION(node->description == nullptr, "you need to free the description"); #endif NS_Free(node->name); #if 0 node->name = nullptr; node->parent = nullptr; node->lastChild = nullptr; node->cachedChild = nullptr; #endif PR_Free(node); } return NS_OK; } nsresult nsSubscribableServer::CreateNode(SubscribeTreeNode *parent, const char *name, SubscribeTreeNode **result) { NS_ASSERTION(result && name, "result or name is null"); if (!result || !name) return NS_ERROR_NULL_POINTER; *result = (SubscribeTreeNode *) PR_Malloc(sizeof(SubscribeTreeNode)); if (!*result) return NS_ERROR_OUT_OF_MEMORY; (*result)->name = strdup(name); if (!(*result)->name) return NS_ERROR_OUT_OF_MEMORY; (*result)->parent = parent; (*result)->prevSibling = nullptr; (*result)->nextSibling = nullptr; (*result)->firstChild = nullptr; (*result)->lastChild = nullptr; (*result)->isSubscribed = false; (*result)->isSubscribable = false; #ifdef HAVE_SUBSCRIBE_DESCRIPTION (*result)->description = nullptr; #endif #ifdef HAVE_SUBSCRIBE_MESSAGES (*result)->messages = 0; #endif (*result)->cachedChild = nullptr; if (parent) { parent->cachedChild = *result; } return NS_OK; } nsresult nsSubscribableServer::AddChildNode(SubscribeTreeNode *parent, const char *name, SubscribeTreeNode **child) { nsresult rv = NS_OK; NS_ASSERTION(parent && child && name, "parent, child or name is null"); if (!parent || !child || !name) return NS_ERROR_NULL_POINTER; if (!parent->firstChild) { // CreateNode will set the parent->cachedChild rv = CreateNode(parent, name, child); NS_ENSURE_SUCCESS(rv,rv); parent->firstChild = *child; parent->lastChild = *child; rv = NotifyAssert(parent, kNC_Child, *child); NS_ENSURE_SUCCESS(rv,rv); return NS_OK; } else { if (parent->cachedChild) { if (strcmp(parent->cachedChild->name,name) == 0) { *child = parent->cachedChild; return NS_OK; } } SubscribeTreeNode *current = parent->firstChild; /* * insert in reverse alphabetical order * this will reduce the # of strcmps * since this is faster assuming: * 1) the hostinfo.dat feeds us the groups in alphabetical order * since we control the hostinfo.dat file, we can guarantee this. * 2) the server gives us the groups in alphabetical order * we can't guarantee this, but it seems to be a common thing * * because we have firstChild, lastChild, nextSibling, prevSibling * we can efficiently reverse the order when dumping to hostinfo.dat * or to GetTargets() */ int32_t compare = strcmp(current->name, name); while (current && (compare != 0)) { if (compare < 0) { // CreateNode will set the parent->cachedChild rv = CreateNode(parent, name, child); NS_ENSURE_SUCCESS(rv,rv); (*child)->nextSibling = current; (*child)->prevSibling = current->prevSibling; current->prevSibling = (*child); if (!(*child)->prevSibling) { parent->firstChild = (*child); } else { (*child)->prevSibling->nextSibling = (*child); } rv = NotifyAssert(parent, kNC_Child, *child); NS_ENSURE_SUCCESS(rv,rv); return NS_OK; } current = current->nextSibling; if (current) { NS_ASSERTION(current->name, "no name!"); compare = strcmp(current->name,name); } else { compare = -1; // anything but 0, since that would be a match } } if (compare == 0) { // already exists; *child = current; // set the cachedChild parent->cachedChild = *child; return NS_OK; } // CreateNode will set the parent->cachedChild rv = CreateNode(parent, name, child); NS_ENSURE_SUCCESS(rv,rv); (*child)->prevSibling = parent->lastChild; (*child)->nextSibling = nullptr; parent->lastChild->nextSibling = *child; parent->lastChild = *child; rv = NotifyAssert(parent, kNC_Child, *child); NS_ENSURE_SUCCESS(rv,rv); return NS_OK; } return NS_OK; } nsresult nsSubscribableServer::FindAndCreateNode(const nsACString &aPath, SubscribeTreeNode **aResult) { nsresult rv = NS_OK; NS_ASSERTION(aResult, "no result"); if (!aResult) return NS_ERROR_NULL_POINTER; if (!mTreeRoot) { // the root has no parent, and its name is server uri rv = CreateNode(nullptr, mIncomingServerUri.get(), &mTreeRoot); NS_ENSURE_SUCCESS(rv,rv); } if (aPath.IsEmpty()) { *aResult = mTreeRoot; return NS_OK; } char *token = nullptr; nsCString pathStr(aPath); char *rest = pathStr.BeginWriting(); // todo do this only once char delimstr[2]; delimstr[0] = mDelimiter; delimstr[1] = '\0'; *aResult = nullptr; SubscribeTreeNode *parent = mTreeRoot; SubscribeTreeNode *child = nullptr; token = NS_strtok(delimstr, &rest); // special case paths that start with the hierarchy delimiter. // We want to include that delimiter in the first token name. if (token && pathStr[0] == mDelimiter) --token; while (token && *token) { rv = AddChildNode(parent, token, &child); if (NS_FAILED(rv)) return rv; token = NS_strtok(delimstr, &rest); parent = child; } // the last child we add is the result *aResult = child; return rv; } NS_IMETHODIMP nsSubscribableServer::HasChildren(const nsACString &aPath, bool *aHasChildren) { nsresult rv = NS_OK; NS_ASSERTION(aHasChildren, "no hasChildren"); if (!aHasChildren) return NS_ERROR_NULL_POINTER; *aHasChildren = false; SubscribeTreeNode *node = nullptr; rv = FindAndCreateNode(aPath, &node); NS_ENSURE_SUCCESS(rv,rv); NS_ASSERTION(node,"didn't find the node"); if (!node) return NS_ERROR_FAILURE; *aHasChildren = (node->firstChild != nullptr); return NS_OK; } NS_IMETHODIMP nsSubscribableServer::IsSubscribed(const nsACString &aPath, bool *aIsSubscribed) { NS_ENSURE_ARG_POINTER(aIsSubscribed); *aIsSubscribed = false; SubscribeTreeNode *node = nullptr; nsresult rv = FindAndCreateNode(aPath, &node); NS_ENSURE_SUCCESS(rv,rv); NS_ASSERTION(node,"didn't find the node"); if (!node) return NS_ERROR_FAILURE; *aIsSubscribed = node->isSubscribed; return NS_OK; } NS_IMETHODIMP nsSubscribableServer::IsSubscribable(const nsACString &aPath, bool *aIsSubscribable) { NS_ENSURE_ARG_POINTER(aIsSubscribable); *aIsSubscribable = false; SubscribeTreeNode *node = nullptr; nsresult rv = FindAndCreateNode(aPath, &node); NS_ENSURE_SUCCESS(rv,rv); NS_ASSERTION(node,"didn't find the node"); if (!node) return NS_ERROR_FAILURE; *aIsSubscribable = node->isSubscribable; return NS_OK; } NS_IMETHODIMP nsSubscribableServer::GetLeafName(const nsACString &aPath, nsAString &aLeafName) { SubscribeTreeNode *node = nullptr; nsresult rv = FindAndCreateNode(aPath, &node); NS_ENSURE_SUCCESS(rv,rv); NS_ASSERTION(node,"didn't find the node"); if (!node) return NS_ERROR_FAILURE; // XXX TODO FIXME // I'm assuming that mShowFullName is true for NNTP, false for IMAP. // for imap, the node name is in modified UTF7 // for news, the path is escaped UTF8 // // when we switch to using the tree, this hack will go away. if (mShowFullName) { return NS_MsgDecodeUnescapeURLPath(aPath, aLeafName); } return CopyMUTF7toUTF16(nsDependentCString(node->name), aLeafName); } NS_IMETHODIMP nsSubscribableServer::GetFirstChildURI(const nsACString &aPath, nsACString &aResult) { aResult.Truncate(); SubscribeTreeNode *node = nullptr; nsresult rv = FindAndCreateNode(aPath, &node); NS_ENSURE_SUCCESS(rv,rv); NS_ASSERTION(node,"didn't find the node"); if (!node) return NS_ERROR_FAILURE; // no children if (!node->firstChild) return NS_ERROR_FAILURE; BuildURIFromNode(node->firstChild, aResult); return NS_OK; } NS_IMETHODIMP nsSubscribableServer::GetChildren(const nsACString &aPath, nsISimpleEnumerator **aResult) { SubscribeTreeNode *node = nullptr; nsresult rv = FindAndCreateNode(aPath, &node); NS_ENSURE_SUCCESS(rv, rv); NS_ASSERTION(node,"didn't find the node"); if (!node) return NS_ERROR_FAILURE; nsAutoCString uriPrefix; NS_ASSERTION(mTreeRoot, "no tree root!"); if (!mTreeRoot) return NS_ERROR_UNEXPECTED; uriPrefix = mTreeRoot->name; // the root's name is the server uri uriPrefix += "/"; if (!aPath.IsEmpty()) { uriPrefix += aPath; uriPrefix += mDelimiter; } // we inserted them in reverse alphabetical order. // so pull them out in reverse to get the right order // in the subscribe dialog SubscribeTreeNode *current = node->lastChild; // return failure if there are no children. if (!current) return NS_ERROR_FAILURE; nsCOMArray result; while (current) { nsAutoCString uri; uri = uriPrefix; NS_ASSERTION(current->name, "no name"); if (!current->name) return NS_ERROR_FAILURE; uri += current->name; nsCOMPtr res; rv = EnsureRDFService(); NS_ENSURE_SUCCESS(rv,rv); // todo, is this creating nsMsgFolders? mRDFService->GetResource(uri, getter_AddRefs(res)); result.AppendObject(res); current = current->prevSibling; } return NS_NewArrayEnumerator(aResult, result); } NS_IMETHODIMP nsSubscribableServer::CommitSubscribeChanges() { NS_ASSERTION(false,"override this."); return NS_ERROR_FAILURE; } NS_IMETHODIMP nsSubscribableServer::SetSearchValue(const nsAString &aSearchValue) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsSubscribableServer::GetSupportsSubscribeSearch(bool *retVal) { return NS_ERROR_NOT_IMPLEMENTED; }