Dactyloidae/mailnews/base/src/nsSubscribableServer.cpp

805 lines
21 KiB
C++

/* -*- 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<nsresult> 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 <nsIRDFResource> subject;
nsCOMPtr <nsIRDFResource> 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 <nsIRDFResource> 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<nsIRDFDataSource> 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<nsIRDFResource> result;
while (current) {
nsAutoCString uri;
uri = uriPrefix;
NS_ASSERTION(current->name, "no name");
if (!current->name)
return NS_ERROR_FAILURE;
uri += current->name;
nsCOMPtr <nsIRDFResource> 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;
}