/* vim: ts=2 sw=2 expandtab
 * BEGIN FLOCK GPL
 *
 * Copyright Flock Inc. 2005-2009
 * http://flock.com
 *
 * This file may be used under the terms of the
 * GNU General Public License Version 2 or later (the "GPL"),
 * http://www.gnu.org/licenses/gpl.html
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * END FLOCK GPL
 */

#include "flockRDFService.h"
#include "flockIRDFSQLDataSource.h"
#include "nsDirectoryServiceUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsIAppStartupNotifier.h"
#include "nsILocalFile.h"
#include "nsIRDFService.h"
#include "nsIRDFContainerUtils.h"
#include "nsIPrefService.h"
#include "nsNetUtil.h"
#include "plstr.h"
#include "prlog.h"

#ifdef PR_LOGGING
static const PRLogModuleInfo *gFlockRDFServiceLog = NULL;
#define LOG_DEBUG(args) PR_LOG(gFlockRDFServiceLog, PR_LOG_DEBUG, args);
#define LOG_ERROR(args) PR_LOG(gFlockRDFServiceLog, PR_LOG_ERROR, args);
#else
#define LOG_DEBUG(args)
#define LOG_ERROR(args)
#endif

#define FLOCK_RDF_FILENAME "flock-data.sqlite"
#define FLOCK_RDF_OLD_FILENAME "flock-data.rdf"

#define FLOCKRDFSETUP_TOPIC "flock-rdf-setup"
#define FLOCKSTARTUP_TOPIC  "flock-startup"

#define FLUSH_DELAY 120000 /* 120 seconds */
#define FLUSH_PREF "flock.flush.delay"

#define NS_DIRECTORYSERVICE_CONTRACTID  "@mozilla.org/file/directory_service;1"
#define NS_RDFSERVICE_CONTRACTID        "@mozilla.org/rdf/rdf-service;1"
#define FLOCK_LOGGER_CONTRACTID         "@flock.com/logger;1"
#define FLOCK_PROFILER_CONTRACTID       "@flock.com/profiler;1"

NS_IMPL_ADDREF(flockRDFService)
NS_IMPL_RELEASE(flockRDFService)
NS_INTERFACE_MAP_BEGIN(flockRDFService)
  NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource)
  NS_INTERFACE_MAP_ENTRY(rdfIDataSource)
  NS_INTERFACE_MAP_ENTRY(nsIRDFRemoteDataSource)
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
  NS_INTERFACE_MAP_ENTRY(flockIRDFObservable)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,nsIRDFDataSource)
NS_INTERFACE_MAP_END

flockRDFService::flockRDFService()
{
#ifdef PR_LOGGING
  if (!gFlockRDFServiceLog)
    gFlockRDFServiceLog = PR_NewLogModule("flockRDFService");
#endif
}

/* nsIRDFDataSource */
NS_IMETHODIMP
flockRDFService::GetURI(char * *aURI)
{
  *aURI = PL_strdup("rdf:flock-favorites");
  return NS_OK;
}

NS_IMETHODIMP
flockRDFService::GetSource(nsIRDFResource *aProperty, nsIRDFNode *aTarget, PRBool aTruthValue, nsIRDFResource **_retval)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->GetSource(aProperty, aTarget, aTruthValue, _retval);
}

NS_IMETHODIMP
flockRDFService::GetSources(nsIRDFResource *aProperty, nsIRDFNode *aTarget, PRBool aTruthValue, nsISimpleEnumerator **_retval)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->GetSources(aProperty, aTarget, aTruthValue, _retval);
}

NS_IMETHODIMP
flockRDFService::GetTarget(nsIRDFResource *aSource, nsIRDFResource *aProperty, PRBool aTruthValue, nsIRDFNode **_retval)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->GetTarget(aSource, aProperty, aTruthValue, _retval);
}

NS_IMETHODIMP
flockRDFService::GetTargets(nsIRDFResource *aSource, nsIRDFResource *aProperty, PRBool aTruthValue, nsISimpleEnumerator **_retval)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->GetTargets(aSource, aProperty, aTruthValue, _retval);
}

NS_IMETHODIMP
flockRDFService::Assert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget, PRBool aTruthValue)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->Assert(aSource, aProperty, aTarget, aTruthValue);
}

NS_IMETHODIMP
flockRDFService::Unassert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->Unassert(aSource, aProperty, aTarget);
}

NS_IMETHODIMP
flockRDFService::Change(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aOldTarget, nsIRDFNode *aNewTarget)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->Change(aSource, aProperty, aOldTarget, aNewTarget);
}

NS_IMETHODIMP
flockRDFService::Move(nsIRDFResource *aOldSource, nsIRDFResource *aNewSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->Move(aOldSource, aNewSource, aProperty, aTarget);
}

NS_IMETHODIMP
flockRDFService::HasAssertion(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget, PRBool aTruthValue, PRBool *_retval)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->HasAssertion(aSource, aProperty, aTarget, aTruthValue, _retval);
}

NS_IMETHODIMP
flockRDFService::AddObserver(nsIRDFObserver *aObserver)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->AddObserver(aObserver);
}

NS_IMETHODIMP
flockRDFService::RemoveObserver(nsIRDFObserver *aObserver)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->RemoveObserver(aObserver);
}

NS_IMETHODIMP
flockRDFService::ArcLabelsIn(nsIRDFNode *aNode, nsISimpleEnumerator **_retval)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->ArcLabelsIn(aNode, _retval);
}

NS_IMETHODIMP
flockRDFService::ArcLabelsOut(nsIRDFResource *aSource, nsISimpleEnumerator **_retval)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->ArcLabelsOut(aSource, _retval);
}

NS_IMETHODIMP
flockRDFService::GetAllResources(nsISimpleEnumerator **_retval)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->GetAllResources(_retval);
}

NS_IMETHODIMP
flockRDFService::IsCommandEnabled(nsISupportsArray *aSources, nsIRDFResource *aCommand, nsISupportsArray *aArguments, PRBool *_retval)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
flockRDFService::DoCommand(nsISupportsArray *aSources, nsIRDFResource *aCommand, nsISupportsArray *aArguments)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
flockRDFService::GetAllCmds(nsIRDFResource *aSource, nsISimpleEnumerator **_retval)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
flockRDFService::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, PRBool *_retval)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->HasArcIn(aNode, aArc,_retval);
}

NS_IMETHODIMP
flockRDFService::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, PRBool *_retval)
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->HasArcOut(aSource, aArc,_retval);
}

NS_IMETHODIMP
flockRDFService::BeginUpdateBatch()
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->BeginUpdateBatch();
}

NS_IMETHODIMP
flockRDFService::EndUpdateBatch()
{
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  return mDataSource->EndUpdateBatch();
}

/* nsIRDFRemoteDataSource */
NS_IMETHODIMP
flockRDFService::GetLoaded(PRBool *aLoaded)
{
  *aLoaded = PR_FALSE;
  if (mRemoteDataSource) {
    return mRemoteDataSource->GetLoaded (aLoaded);
  } else {
    return NS_OK;
  }
}

NS_IMETHODIMP
flockRDFService::Init(const char *aURI)
{
  nsresult rv;
  // we need the RDF service
  nsCOMPtr<nsIRDFService> rdfService =
    do_GetService (NS_RDFSERVICE_CONTRACTID, &rv);
  if (NS_FAILED (rv)) {
    return rv;
  }
  // register ourselves with the RDF service
  return rdfService->RegisterDataSource (this, PR_FALSE);
}

NS_IMETHODIMP
flockRDFService::Refresh(PRBool aBlocking)
{
  if (mRemoteDataSource) {
    return mRemoteDataSource->Refresh (aBlocking);
  } else {
    return NS_ERROR_FAILURE;
  }
}

NS_IMETHODIMP
flockRDFService::Flush()
{
  nsresult rv;
	
  PRInt32 flushEvtID;
  NS_NAMED_LITERAL_STRING(flushEvtName, "f-flush");
  NS_NAMED_LITERAL_STRING(flushEvtMsg, "flock-rdf flush");
  mProfiler->ProfileEventStart(flushEvtName, &flushEvtID);
	
  if (mRemoteDataSource) {
    mLogger->Debug(NS_LITERAL_STRING("starting flush"));
    rv = mRemoteDataSource->Flush ();
    mLogger->Debug(NS_LITERAL_STRING("flush complete"));
  } else {
    rv = NS_ERROR_FAILURE;
  }
  
  mProfiler->ProfileEventEnd(flushEvtID, flushEvtMsg);
  return rv;
}

NS_IMETHODIMP
flockRDFService::FlushTo(const char *aURI)
{
  if (mRemoteDataSource) {
    return mRemoteDataSource->FlushTo (aURI);
  } else {
    return NS_ERROR_FAILURE;
  }
}

/* nsIObserver */
NS_IMETHODIMP
flockRDFService::Observe(nsISupports *aSubject,
                         const char *aTopic,
                         const PRUnichar *data)
{
  if (!PL_strcmp(aTopic, "profile-after-change")) {
    mObserverService->RemoveObserver(this, "profile-after-change");
    Setup();
  } else if (!PL_strcmp(aTopic, "flock-data-startup")) {
    mObserverService->RemoveObserver(this, "flock-data-startup");
    StartupNotify();
  } else if (!PL_strcmp(aTopic, "xpcom-shutdown")) {
    mObserverService->RemoveObserver(this, "xpcom-shutdown");
    Shutdown();
  }

  return NS_OK;
}

/* nsITimerCallback */
NS_IMETHODIMP
flockRDFService::Notify(nsITimer* aTimer) {
  // FIXME: we should only ever be notified about the mFlushTimer
  
  if (mDirty) {
    mDirty = PR_FALSE;
    Flush ();
  }

  nsresult rv = NS_OK;

  PRInt32 flushDelay = FLUSH_DELAY;
  nsCOMPtr<nsIPrefService> prefServ(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  if (prefServ) {
    nsCOMPtr<nsIPrefBranch> prefBranch = nsnull;
    prefServ->GetBranch(nsnull, getter_AddRefs(prefBranch));
    if (prefBranch) {
      PRInt32 prefType;
      prefBranch->GetPrefType(FLUSH_PREF, &prefType);
      if (prefType == nsIPrefBranch::PREF_INT) {
        prefBranch->GetIntPref(FLUSH_PREF, &flushDelay);
      }
    }
  }
  mFlushTimer->InitWithCallback(this, flushDelay, nsITimer::TYPE_ONE_SHOT);

  return NS_OK;
}

/* nsIRDFObserver */
NS_IMETHODIMP
flockRDFService::OnAssert(nsIRDFDataSource *aDataSource,
                          nsIRDFResource *aSource,
                          nsIRDFResource *aProperty,
                          nsIRDFNode *aTarget)
{
  nsresult rv;
  mDirty = PR_TRUE;
  Match(flockIRDFObserver::TYPE_ASSERT, aSource, aProperty, aTarget, nsnull);
  nsCOMPtr<nsIRDFContainerUtils> rdfc =
    do_GetService("@mozilla.org/rdf/container-utils;1", &rv);
  if (NS_FAILED(rv)) {
    return rv;
  }
  PRBool isOrdinal = PR_FALSE;
  rdfc->IsOrdinalProperty(aProperty, &isOrdinal);
  if (isOrdinal) {
    Match(flockIRDFObserver::TYPE_ASSERT_PARENT, aSource, aProperty, aTarget, nsnull);
  }
  return NS_OK;
}

NS_IMETHODIMP
flockRDFService::OnUnassert(nsIRDFDataSource *aDataSource,
                            nsIRDFResource *aSource,
                            nsIRDFResource *aProperty,
                            nsIRDFNode *aTarget)
{
  nsresult rv;
  mDirty = PR_TRUE;
  Match(flockIRDFObserver::TYPE_UNASSERT, aSource, aProperty, aTarget, nsnull);
  nsCOMPtr<nsIRDFContainerUtils> rdfc =
    do_GetService("@mozilla.org/rdf/container-utils;1", &rv);
  if (NS_FAILED(rv)) {
    return rv;
  }
  PRBool isOrdinal = PR_FALSE;
  rdfc->IsOrdinalProperty(aProperty, &isOrdinal);
  if (isOrdinal) {
    Match(flockIRDFObserver::TYPE_UNASSERT_PARENT, aSource, aProperty, aTarget, nsnull);
  }
  return NS_OK;
}

NS_IMETHODIMP
flockRDFService::OnChange(nsIRDFDataSource *aDataSource,
                          nsIRDFResource *aSource,
                          nsIRDFResource *aProperty,
                          nsIRDFNode *aOldTarget,
                          nsIRDFNode *aNewTarget)
{
  mDirty = PR_TRUE;
  Match(flockIRDFObserver::TYPE_CHANGE, aSource, aProperty, aNewTarget, aOldTarget);
  return NS_OK;
}

NS_IMETHODIMP
flockRDFService::OnMove(nsIRDFDataSource *aDataSource,
                        nsIRDFResource *aOldSource,
                        nsIRDFResource *aNewSource,
                        nsIRDFResource *aProperty,
                        nsIRDFNode *aTarget)
{
  // JMC - Will implement move match later if required
  // Will require another change to interface
  mDirty = PR_TRUE;
  Match(flockIRDFObserver::TYPE_UNASSERT, aOldSource, aProperty, aTarget, nsnull);
  Match(flockIRDFObserver::TYPE_ASSERT, aNewSource, aProperty, aTarget, nsnull);
  return NS_OK;
}

NS_IMETHODIMP
flockRDFService::OnBeginUpdateBatch(nsIRDFDataSource *aDataSource)
{
  return NS_OK;
}

NS_IMETHODIMP
flockRDFService::OnEndUpdateBatch(nsIRDFDataSource *aDataSource)
{
  return NS_OK;
}

/* flockIRDFObservable */
NS_IMETHODIMP
flockRDFService::AddArcObserver(PRUint16 aType,
                                nsIRDFResource *aSubject,
                                nsIRDFResource *aPredicate,
                                nsIRDFNode *aObject,
                                flockIRDFObserver *aObserver)
{
  flockRDFService::Pattern* pattern;
  pattern = new Pattern(aType, aSubject, aPredicate, aObject, aObserver);
  mPatterns.AppendObject(pattern);
  return NS_OK;
}

NS_IMETHODIMP
flockRDFService::RemoveArcObserver(PRUint16 aType,
                                   nsIRDFResource *aSubject,
                                   nsIRDFResource *aPredicate,
                                   nsIRDFNode *aObject,
                                   flockIRDFObserver *aObserver)
{
  PRBool found = PR_FALSE;

  nsCOMArray<Pattern> oldPatterns(mPatterns);
  mPatterns.Clear();

  for (int i = 0; i < oldPatterns.Count(); i++) {
    Pattern* pattern = oldPatterns[i];

    if (!found) {
      Pattern testPattern(aType, aSubject, aPredicate, aObject, aObserver);
      if (pattern->Equals(&testPattern)) {
        found = PR_TRUE;
        continue;
      }
    }

    mPatterns.AppendObject(pattern);
  }

  return found ? NS_OK : NS_ERROR_FAILURE;
}


NS_IMPL_ISUPPORTS0(flockRDFService::Pattern)

PRBool
flockRDFService::Pattern::Equals(Pattern *aPattern)
{
  nsresult rv;
  PRBool matches = PR_TRUE;

  if (mType != aPattern->mType) {
    return PR_FALSE;
  }

  if ((mSubject == nsnull && aPattern->mSubject != nsnull) ||
      (mSubject != nsnull && aPattern->mSubject == nsnull)) {
    return PR_FALSE;
  }

  if (aPattern->mSubject != nsnull) {
    rv = aPattern->mSubject->EqualsNode (mSubject, &matches);
    if (!matches || NS_FAILED (rv)) {
      return PR_FALSE;
    }
  }

  if ((mPredicate == nsnull && aPattern->mPredicate != nsnull) ||
      (mPredicate != nsnull && aPattern->mPredicate == nsnull)) {
    return PR_FALSE;
  }

  if (aPattern->mPredicate != nsnull) {
    rv = aPattern->mPredicate->EqualsNode (mPredicate, &matches);
    if (!matches || NS_FAILED (rv)) {
      return PR_FALSE;
    }
  }

  if ((mObject == nsnull && aPattern->mObject != nsnull) ||
      (mObject != nsnull && aPattern->mObject == nsnull)) {
    return PR_FALSE;
  }

  if (aPattern->mObject != nsnull) {
    rv = aPattern->mObject->EqualsNode (mObject, &matches);
    if (!matches || NS_FAILED (rv)) {
      return PR_FALSE;
    }
  }

  return (mObserver == aPattern->mObserver);
}

NS_IMETHODIMP
flockRDFService::Pattern::Match(nsIRDFDataSource* aDataSource,
                                PRUint16 aType,
                                nsIRDFResource* aSubject,
                                nsIRDFResource* aPredicate,
                                nsIRDFNode* aObject,
                                nsIRDFNode* aOldObject)
{
  nsresult rv = NS_OK;
  PRBool matches = PR_TRUE;

  if ((mType & aType) == 0) {
    return rv;
  }

  if (mSubject != nsnull) {
    rv = mSubject->EqualsNode (aSubject, &matches);
    if (!matches || NS_FAILED (rv)) {
      return rv;
    }
  }

  if (mPredicate != nsnull) {
    rv = mPredicate->EqualsNode (aPredicate, &matches);
    if (!matches || NS_FAILED (rv)) {
      return rv;
    }
  }

  if (mObject != nsnull) {
    rv = mObject->EqualsNode (aObject, &matches);
    if (!matches || NS_FAILED (rv)) {
      return rv;
    }
  }

  rv = mObserver->RdfChanged (aDataSource, aType, aSubject, aPredicate, 
      aObject, aOldObject);

  return rv;
}

NS_IMETHODIMP
flockRDFService::Match(PRUint16 aType,
                       nsIRDFResource* aSubject,
                       nsIRDFResource* aPredicate,
                       nsIRDFNode* aObject,
                       nsIRDFNode* aOldObject)
{
  for (int i=0; i<mPatterns.Count(); i++) {
    mPatterns[i]->Match(this, aType, aSubject, aPredicate, aObject, aOldObject);
  }

  return NS_OK;
}


/* initialization code */
nsresult
flockRDFService::Initialize()
{
  LOG_DEBUG(("flockRDFService::Initialize() starts\n"));
  nsresult rv = NS_OK;

  mLogger = do_CreateInstance (FLOCK_LOGGER_CONTRACTID);
  if (!mLogger) {
    return NS_ERROR_FAILURE;
  }

  mProfiler = do_GetService(FLOCK_PROFILER_CONTRACTID);
  if (!mProfiler) {
    return NS_ERROR_FAILURE;
  }

  rv = mLogger->Init(NS_LITERAL_STRING("flockRDFService"));
  if (NS_FAILED (rv)) {
    return rv;
  }

  rv = mLogger->Debug(NS_LITERAL_STRING("initializing"));
  if (NS_FAILED (rv)) {
    return rv;
  }

  mObserverService = do_GetService ("@mozilla.org/observer-service;1");
  if (!mObserverService) {
    return NS_ERROR_FAILURE;
  }

  rv = mObserverService->AddObserver (this, "profile-after-change", PR_FALSE);
  if (NS_FAILED (rv)) {
    return rv;
  }

  rv = mObserverService->AddObserver (this, "flock-data-startup", PR_FALSE);
  if (NS_FAILED (rv)) {
    return rv;
  }

  rv = mObserverService->AddObserver (this, "xpcom-shutdown", PR_FALSE);
  if (NS_FAILED (rv)) {
    return rv;
  }

  return rv;
}

nsresult
flockRDFService::Setup()
{
  LOG_DEBUG(("flockRDFService::Setup() starts\n"));

  nsresult rv = NS_OK;

  rv = mLogger->Info(NS_LITERAL_STRING("starting up..."));
  if (NS_FAILED (rv)) {
    return rv;
  }

  mDirty = PR_FALSE;

  // get the profile directory
  nsCOMPtr<nsIFile> profileDirectory;
  rv = NS_GetSpecialDirectory ("ProfD", getter_AddRefs (profileDirectory));
  if (NS_FAILED (rv)) {
    LOG_ERROR(("flockRDFService::Setup() can't get the profile directory\n"));
    return rv;
  }

  // get the rdf file name
  nsCOMPtr<nsIFile> rdfFile;
  rv = profileDirectory->Clone(getter_AddRefs(rdfFile));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = rdfFile->Append(NS_LITERAL_STRING(FLOCK_RDF_FILENAME));
  NS_ENSURE_SUCCESS(rv, rv);

  // check if we exist; if not, we need to migrate
  PRBool rdfFileAlreadyExists;
  rv = rdfFile->Exists(&rdfFileAlreadyExists);
  NS_ENSURE_SUCCESS(rv, rv);

  // get a uri for the file
  nsCString url;
  rv = NS_GetURLSpecFromFile (rdfFile, url);
  if (NS_FAILED (rv)) {
    return rv;
  }

  // we need the RDF service
  nsCOMPtr<nsIRDFService> rdfService =
    do_GetService (NS_RDFSERVICE_CONTRACTID, &rv);
  if (NS_FAILED (rv)) {
    return rv;
  }

  rv = mLogger->Debug(NS_LITERAL_STRING("starting rdf load"));
  if (NS_FAILED (rv)) {
    return rv;
  }

  // so we can load the RDF from disk
  rv = rdfService->GetDataSourceBlocking (PromiseFlatCString(url).get (),
                                          getter_AddRefs (mDataSource));
  if (NS_FAILED (rv)) {
    return rv;
  }

  rv = mLogger->Debug(NS_LITERAL_STRING("finished rdf load"));
  if (NS_FAILED (rv)) {
    return rv;
  }

  if (!rdfFileAlreadyExists) {
    // Maybe it's migration time
    mLogger->Debug(NS_LITERAL_STRING("checking if old RDF XML exists"));

    nsCOMPtr<nsIFile> oldRdfFile;
    rv = profileDirectory->Clone(getter_AddRefs(oldRdfFile));
    NS_ENSURE_SUCCESS(rv, rv);

    rv = oldRdfFile->Append(NS_LITERAL_STRING(FLOCK_RDF_OLD_FILENAME));
    NS_ENSURE_SUCCESS(rv, rv);

    PRBool oldRdfFileExists;
    rv = oldRdfFile->Exists(&oldRdfFileExists);
    NS_ENSURE_SUCCESS(rv, rv);

    if (oldRdfFileExists) {
      mLogger->Debug(NS_LITERAL_STRING("old RDF XML found, migrating..."));

      nsCOMPtr<nsIURI> oldFileURI;
      rv = NS_NewFileURI(getter_AddRefs(oldFileURI), oldRdfFile);
      NS_ENSURE_SUCCESS(rv, rv);

      nsCOMPtr<flockIRDFSQLDataSource> sqlDataSource =
        do_QueryInterface(mDataSource);

      rv = sqlDataSource->ImportRDFXML(oldFileURI);
      if (NS_SUCCEEDED(rv)) {
        mLogger->Debug(NS_LITERAL_STRING("migration complete"));
      } else {
        mLogger->Debug(NS_LITERAL_STRING("migration failed"));
      }
    } else {
      mLogger->Debug(NS_LITERAL_STRING("no old RDF XML found"));
    }
  }

  // we want to watch it
  rv = mDataSource->AddObserver(this);
  NS_ENSURE_SUCCESS(rv, rv);

  // we want an nsIRDFRemoteDataSource representation
  mRemoteDataSource = do_QueryInterface(mDataSource);

  // start the indexer service
  mIndexerService = do_GetService ("@flock.com/indexer;1", &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  mIndexerService->Init();

  // tell early startup components to go
  LOG_DEBUG(("flockRDFService::Setup() starting " FLOCKRDFSETUP_TOPIC " components\n"));
  nsCOMPtr<nsIObserver> startupNotifier;
  startupNotifier = do_CreateInstance (NS_APPSTARTUPNOTIFIER_CONTRACTID);
  startupNotifier->Observe(NULL, FLOCKRDFSETUP_TOPIC, NULL);

  PRInt32 flushDelay = FLUSH_DELAY;
  nsCOMPtr<nsIPrefService> prefServ(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  if (prefServ) {
    nsCOMPtr<nsIPrefBranch> prefBranch = nsnull;
    prefServ->GetBranch(nsnull, getter_AddRefs(prefBranch));
    if (prefBranch) {
      PRInt32 prefType;
      prefBranch->GetPrefType(FLUSH_PREF, &prefType);
      if (prefType == nsIPrefBranch::PREF_INT) {
        prefBranch->GetIntPref(FLUSH_PREF, &flushDelay);
      }
    }
  }

  mFlushTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
  mFlushTimer->InitWithCallback(this, flushDelay, nsITimer::TYPE_ONE_SHOT);

  return rv;
}

nsresult
flockRDFService::StartupNotify()
{
  LOG_DEBUG(("flockRDFService::StartupNotify() starts\n"));

  nsCOMPtr<nsIObserver> startupNotifier;
  startupNotifier = do_CreateInstance (NS_APPSTARTUPNOTIFIER_CONTRACTID);

  LOG_DEBUG(("flockRDFService::StartupNotify() starting flock-startup components\n"));
  NS_NAMED_LITERAL_STRING(startupEvtName, "f-s");
  NS_NAMED_LITERAL_STRING(startupEvtMsg, "flock-startup notifications");
  PRInt32 startupEvtID;
  mProfiler->ProfileEventStart(startupEvtName, &startupEvtID);
  startupNotifier->Observe(NULL, FLOCKSTARTUP_TOPIC, NULL);
  mProfiler->ProfileEventEnd(startupEvtID, startupEvtMsg);
  LOG_DEBUG(("flockRDFService::StartupNotify() started flock-startup components\n"));
  return NS_OK;
}

nsresult
flockRDFService::Shutdown()
{
  LOG_DEBUG(("flockRDFService::Shutdown() starts\n"));

  mObserverService->NotifyObservers(nsnull, "flock-shutdown", nsnull);

  if (mFlushTimer) {
    mFlushTimer->Cancel();
    mFlushTimer = nsnull;
  }

  if (mDirty) {
    mDirty = PR_FALSE;

    if (mRemoteDataSource) {
      LOG_DEBUG(("flockRDFService::Shutdown() flushed data source\n"));
      mRemoteDataSource->Flush();
    }
  }

  if (mDataSource) {
    mDataSource->RemoveObserver(this);

    nsCOMPtr<flockIRDFSQLDataSource> sqlDataSource =
      do_QueryInterface(mDataSource);

    if (sqlDataSource) {
      LOG_DEBUG(("flockRDFService::Shutdown() shutdown data source\n"));
      sqlDataSource->Shutdown();
    }
  }

  mRemoteDataSource = nsnull;
  mDataSource = nsnull;

  return NS_OK;
}


NS_IMETHODIMP
flockRDFService::VisitAllSubjects(rdfITripleVisitor *aVisitor)
{
  nsresult rv;
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  nsCOMPtr<rdfIDataSource> rdfds = do_QueryInterface(mDataSource, &rv);
  if (NS_FAILED(rv)) {
    return rv;
  }
  return rdfds->VisitAllSubjects(aVisitor);
}

NS_IMETHODIMP
flockRDFService::VisitAllTriples(rdfITripleVisitor *aVisitor)
{
  nsresult rv;
  if (!mDataSource) {
    return NS_ERROR_FAILURE;
  }
  nsCOMPtr<rdfIDataSource> rdfds = do_QueryInterface(mDataSource, &rv);
  if (NS_FAILED(rv)) {
    return rv;
  }
  return rdfds->VisitAllTriples(aVisitor);
}


/* singleton code */
static flockRDFService* gSingleton = nsnull;

flockRDFService*
flockRDFService::GetSingleton()
{
  if (!gSingleton) {
    gSingleton = new flockRDFService();
    if (!gSingleton) {
      return nsnull;
    }

    NS_ADDREF(gSingleton);
    if (NS_FAILED(gSingleton->Initialize())) {
      NS_RELEASE(gSingleton);
    }
  }
  return gSingleton;
}
