diff options
Diffstat (limited to 'toolkit/components/places/nsAnnotationService.cpp')
-rw-r--r-- | toolkit/components/places/nsAnnotationService.cpp | 1990 |
1 files changed, 1990 insertions, 0 deletions
diff --git a/toolkit/components/places/nsAnnotationService.cpp b/toolkit/components/places/nsAnnotationService.cpp new file mode 100644 index 000000000..9d62bd34a --- /dev/null +++ b/toolkit/components/places/nsAnnotationService.cpp @@ -0,0 +1,1990 @@ +/* -*- Mode: C++; tab-width: 8; 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 "mozilla/ArrayUtils.h" + +#include "nsAnnotationService.h" +#include "nsNavHistory.h" +#include "nsPlacesTables.h" +#include "nsPlacesIndexes.h" +#include "nsPlacesMacros.h" +#include "Helpers.h" + +#include "nsNetUtil.h" +#include "nsIVariant.h" +#include "nsString.h" +#include "nsVariant.h" +#include "mozilla/storage.h" + +#include "GeckoProfiler.h" + +#include "nsNetCID.h" + +using namespace mozilla; +using namespace mozilla::places; + +#define ENSURE_ANNO_TYPE(_type, _statement) \ + PR_BEGIN_MACRO \ + int32_t type = _statement->AsInt32(kAnnoIndex_Type); \ + NS_ENSURE_TRUE(type == nsIAnnotationService::_type, NS_ERROR_INVALID_ARG); \ + PR_END_MACRO + +#define NOTIFY_ANNOS_OBSERVERS(_notification) \ + PR_BEGIN_MACRO \ + for (int32_t i = 0; i < mObservers.Count(); i++) \ + mObservers[i]->_notification; \ + PR_END_MACRO + +const int32_t nsAnnotationService::kAnnoIndex_ID = 0; +const int32_t nsAnnotationService::kAnnoIndex_PageOrItem = 1; +const int32_t nsAnnotationService::kAnnoIndex_NameID = 2; +const int32_t nsAnnotationService::kAnnoIndex_Content = 3; +const int32_t nsAnnotationService::kAnnoIndex_Flags = 4; +const int32_t nsAnnotationService::kAnnoIndex_Expiration = 5; +const int32_t nsAnnotationService::kAnnoIndex_Type = 6; +const int32_t nsAnnotationService::kAnnoIndex_DateAdded = 7; +const int32_t nsAnnotationService::kAnnoIndex_LastModified = 8; + +namespace mozilla { +namespace places { + +//////////////////////////////////////////////////////////////////////////////// +//// AnnotatedResult + +AnnotatedResult::AnnotatedResult(const nsCString& aGUID, + nsIURI* aURI, + int64_t aItemId, + const nsACString& aAnnotationName, + nsIVariant* aAnnotationValue) +: mGUID(aGUID) +, mURI(aURI) +, mItemId(aItemId) +, mAnnotationName(aAnnotationName) +, mAnnotationValue(aAnnotationValue) +{ +} + +AnnotatedResult::~AnnotatedResult() +{ +} + +NS_IMETHODIMP +AnnotatedResult::GetGuid(nsACString& _guid) +{ + _guid = mGUID; + return NS_OK; +} + +NS_IMETHODIMP +AnnotatedResult::GetUri(nsIURI** _uri) +{ + NS_IF_ADDREF(*_uri = mURI); + return NS_OK; +} + +NS_IMETHODIMP +AnnotatedResult::GetItemId(int64_t* _itemId) +{ + *_itemId = mItemId; + return NS_OK; +} + +NS_IMETHODIMP +AnnotatedResult::GetAnnotationName(nsACString& _annotationName) +{ + _annotationName = mAnnotationName; + return NS_OK; +} + +NS_IMETHODIMP +AnnotatedResult::GetAnnotationValue(nsIVariant** _annotationValue) +{ + NS_IF_ADDREF(*_annotationValue = mAnnotationValue); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(AnnotatedResult, mozIAnnotatedResult) + +} // namespace places +} // namespace mozilla + +PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsAnnotationService, gAnnotationService) + +NS_IMPL_ISUPPORTS(nsAnnotationService +, nsIAnnotationService +, nsIObserver +, nsISupportsWeakReference +) + + +nsAnnotationService::nsAnnotationService() + : mHasSessionAnnotations(false) +{ + NS_ASSERTION(!gAnnotationService, + "Attempting to create two instances of the service!"); + gAnnotationService = this; +} + + +nsAnnotationService::~nsAnnotationService() +{ + NS_ASSERTION(gAnnotationService == this, + "Deleting a non-singleton instance of the service"); + if (gAnnotationService == this) + gAnnotationService = nullptr; +} + + +nsresult +nsAnnotationService::Init() +{ + mDB = Database::GetDatabase(); + NS_ENSURE_STATE(mDB); + + nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService(); + if (obsSvc) { + (void)obsSvc->AddObserver(this, TOPIC_PLACES_SHUTDOWN, true); + } + + return NS_OK; +} + +nsresult +nsAnnotationService::SetAnnotationStringInternal(nsIURI* aURI, + int64_t aItemId, + const nsACString& aName, + const nsAString& aValue, + int32_t aFlags, + uint16_t aExpiration) +{ + mozStorageTransaction transaction(mDB->MainConn(), false); + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartSetAnnotation(aURI, aItemId, aName, aFlags, aExpiration, + nsIAnnotationService::TYPE_STRING, + statement); + NS_ENSURE_SUCCESS(rv, rv); + mozStorageStatementScoper scoper(statement); + + rv = statement->BindStringByName(NS_LITERAL_CSTRING("content"), aValue); + NS_ENSURE_SUCCESS(rv, rv); + + rv = statement->Execute(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = transaction.Commit(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::SetPageAnnotation(nsIURI* aURI, + const nsACString& aName, + nsIVariant* aValue, + int32_t aFlags, + uint16_t aExpiration) +{ + NS_ENSURE_ARG(aURI); + NS_ENSURE_ARG(aValue); + + uint16_t dataType; + nsresult rv = aValue->GetDataType(&dataType); + NS_ENSURE_SUCCESS(rv, rv); + + switch (dataType) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_BOOL: { + int32_t valueInt; + rv = aValue->GetAsInt32(&valueInt); + if (NS_SUCCEEDED(rv)) { + NS_ENSURE_SUCCESS(rv, rv); + rv = SetPageAnnotationInt32(aURI, aName, valueInt, aFlags, aExpiration); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + // Fall through int64_t case otherwise. + MOZ_FALLTHROUGH; + } + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT64: { + int64_t valueLong; + rv = aValue->GetAsInt64(&valueLong); + if (NS_SUCCEEDED(rv)) { + NS_ENSURE_SUCCESS(rv, rv); + rv = SetPageAnnotationInt64(aURI, aName, valueLong, aFlags, aExpiration); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + // Fall through double case otherwise. + MOZ_FALLTHROUGH; + } + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: { + double valueDouble; + rv = aValue->GetAsDouble(&valueDouble); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetPageAnnotationDouble(aURI, aName, valueDouble, aFlags, aExpiration); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_WCHAR: + case nsIDataType::VTYPE_DOMSTRING: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_ASTRING: { + nsAutoString stringValue; + rv = aValue->GetAsAString(stringValue); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetPageAnnotationString(aURI, aName, stringValue, aFlags, aExpiration); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + } + + return NS_ERROR_NOT_IMPLEMENTED; +} + + +NS_IMETHODIMP +nsAnnotationService::SetItemAnnotation(int64_t aItemId, + const nsACString& aName, + nsIVariant* aValue, + int32_t aFlags, + uint16_t aExpiration, + uint16_t aSource) +{ + PROFILER_LABEL("AnnotationService", "SetItemAnnotation", + js::ProfileEntry::Category::OTHER); + + NS_ENSURE_ARG_MIN(aItemId, 1); + NS_ENSURE_ARG(aValue); + + if (aExpiration == EXPIRE_WITH_HISTORY) + return NS_ERROR_INVALID_ARG; + + uint16_t dataType; + nsresult rv = aValue->GetDataType(&dataType); + NS_ENSURE_SUCCESS(rv, rv); + + switch (dataType) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_BOOL: { + int32_t valueInt; + rv = aValue->GetAsInt32(&valueInt); + if (NS_SUCCEEDED(rv)) { + NS_ENSURE_SUCCESS(rv, rv); + rv = SetItemAnnotationInt32(aItemId, aName, valueInt, aFlags, aExpiration, aSource); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + // Fall through int64_t case otherwise. + MOZ_FALLTHROUGH; + } + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT64: { + int64_t valueLong; + rv = aValue->GetAsInt64(&valueLong); + if (NS_SUCCEEDED(rv)) { + NS_ENSURE_SUCCESS(rv, rv); + rv = SetItemAnnotationInt64(aItemId, aName, valueLong, aFlags, aExpiration, aSource); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + // Fall through double case otherwise. + MOZ_FALLTHROUGH; + } + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: { + double valueDouble; + rv = aValue->GetAsDouble(&valueDouble); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetItemAnnotationDouble(aItemId, aName, valueDouble, aFlags, aExpiration, aSource); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_WCHAR: + case nsIDataType::VTYPE_DOMSTRING: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_ASTRING: { + nsAutoString stringValue; + rv = aValue->GetAsAString(stringValue); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetItemAnnotationString(aItemId, aName, stringValue, aFlags, aExpiration, aSource); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + } + + return NS_ERROR_NOT_IMPLEMENTED; +} + + +NS_IMETHODIMP +nsAnnotationService::SetPageAnnotationString(nsIURI* aURI, + const nsACString& aName, + const nsAString& aValue, + int32_t aFlags, + uint16_t aExpiration) +{ + NS_ENSURE_ARG(aURI); + + nsresult rv = SetAnnotationStringInternal(aURI, 0, aName, aValue, + aFlags, aExpiration); + NS_ENSURE_SUCCESS(rv, rv); + + NOTIFY_ANNOS_OBSERVERS(OnPageAnnotationSet(aURI, aName)); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::SetItemAnnotationString(int64_t aItemId, + const nsACString& aName, + const nsAString& aValue, + int32_t aFlags, + uint16_t aExpiration, + uint16_t aSource) +{ + NS_ENSURE_ARG_MIN(aItemId, 1); + + if (aExpiration == EXPIRE_WITH_HISTORY) + return NS_ERROR_INVALID_ARG; + + nsresult rv = SetAnnotationStringInternal(nullptr, aItemId, aName, aValue, + aFlags, aExpiration); + NS_ENSURE_SUCCESS(rv, rv); + + NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName, aSource)); + + return NS_OK; +} + + +nsresult +nsAnnotationService::SetAnnotationInt32Internal(nsIURI* aURI, + int64_t aItemId, + const nsACString& aName, + int32_t aValue, + int32_t aFlags, + uint16_t aExpiration) +{ + mozStorageTransaction transaction(mDB->MainConn(), false); + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartSetAnnotation(aURI, aItemId, aName, aFlags, aExpiration, + nsIAnnotationService::TYPE_INT32, + statement); + NS_ENSURE_SUCCESS(rv, rv); + mozStorageStatementScoper scoper(statement); + + rv = statement->BindInt32ByName(NS_LITERAL_CSTRING("content"), aValue); + NS_ENSURE_SUCCESS(rv, rv); + + rv = statement->Execute(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = transaction.Commit(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::SetPageAnnotationInt32(nsIURI* aURI, + const nsACString& aName, + int32_t aValue, + int32_t aFlags, + uint16_t aExpiration) +{ + NS_ENSURE_ARG(aURI); + + nsresult rv = SetAnnotationInt32Internal(aURI, 0, aName, aValue, + aFlags, aExpiration); + NS_ENSURE_SUCCESS(rv, rv); + + NOTIFY_ANNOS_OBSERVERS(OnPageAnnotationSet(aURI, aName)); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::SetItemAnnotationInt32(int64_t aItemId, + const nsACString& aName, + int32_t aValue, + int32_t aFlags, + uint16_t aExpiration, + uint16_t aSource) +{ + NS_ENSURE_ARG_MIN(aItemId, 1); + + if (aExpiration == EXPIRE_WITH_HISTORY) + return NS_ERROR_INVALID_ARG; + + nsresult rv = SetAnnotationInt32Internal(nullptr, aItemId, aName, aValue, + aFlags, aExpiration); + NS_ENSURE_SUCCESS(rv, rv); + + NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName, aSource)); + + return NS_OK; +} + + +nsresult +nsAnnotationService::SetAnnotationInt64Internal(nsIURI* aURI, + int64_t aItemId, + const nsACString& aName, + int64_t aValue, + int32_t aFlags, + uint16_t aExpiration) +{ + mozStorageTransaction transaction(mDB->MainConn(), false); + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartSetAnnotation(aURI, aItemId, aName, aFlags, aExpiration, + nsIAnnotationService::TYPE_INT64, + statement); + NS_ENSURE_SUCCESS(rv, rv); + mozStorageStatementScoper scoper(statement); + + rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("content"), aValue); + NS_ENSURE_SUCCESS(rv, rv); + + rv = statement->Execute(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = transaction.Commit(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::SetPageAnnotationInt64(nsIURI* aURI, + const nsACString& aName, + int64_t aValue, + int32_t aFlags, + uint16_t aExpiration) +{ + NS_ENSURE_ARG(aURI); + + nsresult rv = SetAnnotationInt64Internal(aURI, 0, aName, aValue, + aFlags, aExpiration); + NS_ENSURE_SUCCESS(rv, rv); + + NOTIFY_ANNOS_OBSERVERS(OnPageAnnotationSet(aURI, aName)); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::SetItemAnnotationInt64(int64_t aItemId, + const nsACString& aName, + int64_t aValue, + int32_t aFlags, + uint16_t aExpiration, + uint16_t aSource) +{ + NS_ENSURE_ARG_MIN(aItemId, 1); + + if (aExpiration == EXPIRE_WITH_HISTORY) + return NS_ERROR_INVALID_ARG; + + nsresult rv = SetAnnotationInt64Internal(nullptr, aItemId, aName, aValue, + aFlags, aExpiration); + NS_ENSURE_SUCCESS(rv, rv); + + NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName, aSource)); + + return NS_OK; +} + + +nsresult +nsAnnotationService::SetAnnotationDoubleInternal(nsIURI* aURI, + int64_t aItemId, + const nsACString& aName, + double aValue, + int32_t aFlags, + uint16_t aExpiration) +{ + mozStorageTransaction transaction(mDB->MainConn(), false); + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartSetAnnotation(aURI, aItemId, aName, aFlags, aExpiration, + nsIAnnotationService::TYPE_DOUBLE, + statement); + NS_ENSURE_SUCCESS(rv, rv); + mozStorageStatementScoper scoper(statement); + + rv = statement->BindDoubleByName(NS_LITERAL_CSTRING("content"), aValue); + NS_ENSURE_SUCCESS(rv, rv); + + rv = statement->Execute(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = transaction.Commit(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::SetPageAnnotationDouble(nsIURI* aURI, + const nsACString& aName, + double aValue, + int32_t aFlags, + uint16_t aExpiration) +{ + NS_ENSURE_ARG(aURI); + + nsresult rv = SetAnnotationDoubleInternal(aURI, 0, aName, aValue, + aFlags, aExpiration); + NS_ENSURE_SUCCESS(rv, rv); + + NOTIFY_ANNOS_OBSERVERS(OnPageAnnotationSet(aURI, aName)); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::SetItemAnnotationDouble(int64_t aItemId, + const nsACString& aName, + double aValue, + int32_t aFlags, + uint16_t aExpiration, + uint16_t aSource) +{ + NS_ENSURE_ARG_MIN(aItemId, 1); + + if (aExpiration == EXPIRE_WITH_HISTORY) + return NS_ERROR_INVALID_ARG; + + nsresult rv = SetAnnotationDoubleInternal(nullptr, aItemId, aName, aValue, + aFlags, aExpiration); + NS_ENSURE_SUCCESS(rv, rv); + + NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName, aSource)); + + return NS_OK; +} + +NS_IMETHODIMP +nsAnnotationService::GetPageAnnotationString(nsIURI* aURI, + const nsACString& aName, + nsAString& _retval) +{ + NS_ENSURE_ARG(aURI); + + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartGetAnnotation(aURI, 0, aName, statement); + if (NS_FAILED(rv)) + return rv; + + mozStorageStatementScoper scoper(statement); + ENSURE_ANNO_TYPE(TYPE_STRING, statement); + rv = statement->GetString(kAnnoIndex_Content, _retval); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetItemAnnotationString(int64_t aItemId, + const nsACString& aName, + nsAString& _retval) +{ + NS_ENSURE_ARG_MIN(aItemId, 1); + + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartGetAnnotation(nullptr, aItemId, aName, statement); + if (NS_FAILED(rv)) + return rv; + + mozStorageStatementScoper scoper(statement); + ENSURE_ANNO_TYPE(TYPE_STRING, statement); + rv = statement->GetString(kAnnoIndex_Content, _retval); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetPageAnnotation(nsIURI* aURI, + const nsACString& aName, + nsIVariant** _retval) +{ + NS_ENSURE_ARG(aURI); + NS_ENSURE_ARG_POINTER(_retval); + + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartGetAnnotation(aURI, 0, aName, statement); + if (NS_FAILED(rv)) + return rv; + + mozStorageStatementScoper scoper(statement); + + nsCOMPtr<nsIWritableVariant> value = new nsVariant(); + int32_t type = statement->AsInt32(kAnnoIndex_Type); + switch (type) { + case nsIAnnotationService::TYPE_INT32: + case nsIAnnotationService::TYPE_INT64: + case nsIAnnotationService::TYPE_DOUBLE: { + rv = value->SetAsDouble(statement->AsDouble(kAnnoIndex_Content)); + break; + } + case nsIAnnotationService::TYPE_STRING: { + nsAutoString valueString; + rv = statement->GetString(kAnnoIndex_Content, valueString); + if (NS_SUCCEEDED(rv)) + rv = value->SetAsAString(valueString); + break; + } + default: { + rv = NS_ERROR_UNEXPECTED; + break; + } + } + + if (NS_SUCCEEDED(rv)) { + value.forget(_retval); + } + + return rv; +} + + +NS_IMETHODIMP +nsAnnotationService::GetItemAnnotation(int64_t aItemId, + const nsACString& aName, + nsIVariant** _retval) +{ + NS_ENSURE_ARG_MIN(aItemId, 1); + NS_ENSURE_ARG_POINTER(_retval); + + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartGetAnnotation(nullptr, aItemId, aName, statement); + if (NS_FAILED(rv)) + return rv; + + mozStorageStatementScoper scoper(statement); + + nsCOMPtr<nsIWritableVariant> value = new nsVariant(); + int32_t type = statement->AsInt32(kAnnoIndex_Type); + switch (type) { + case nsIAnnotationService::TYPE_INT32: + case nsIAnnotationService::TYPE_INT64: + case nsIAnnotationService::TYPE_DOUBLE: { + rv = value->SetAsDouble(statement->AsDouble(kAnnoIndex_Content)); + break; + } + case nsIAnnotationService::TYPE_STRING: { + nsAutoString valueString; + rv = statement->GetString(kAnnoIndex_Content, valueString); + if (NS_SUCCEEDED(rv)) + rv = value->SetAsAString(valueString); + break; + } + default: { + rv = NS_ERROR_UNEXPECTED; + break; + } + } + + if (NS_SUCCEEDED(rv)) { + value.forget(_retval); + } + + return rv; +} + + +NS_IMETHODIMP +nsAnnotationService::GetPageAnnotationInt32(nsIURI* aURI, + const nsACString& aName, + int32_t* _retval) +{ + NS_ENSURE_ARG(aURI); + NS_ENSURE_ARG_POINTER(_retval); + + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartGetAnnotation(aURI, 0, aName, statement); + if (NS_FAILED(rv)) + return rv; + + mozStorageStatementScoper scoper(statement); + ENSURE_ANNO_TYPE(TYPE_INT32, statement); + *_retval = statement->AsInt32(kAnnoIndex_Content); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetItemAnnotationInt32(int64_t aItemId, + const nsACString& aName, + int32_t* _retval) +{ + NS_ENSURE_ARG_MIN(aItemId, 1); + NS_ENSURE_ARG_POINTER(_retval); + + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartGetAnnotation(nullptr, aItemId, aName, statement); + if (NS_FAILED(rv)) + return rv; + + mozStorageStatementScoper scoper(statement); + ENSURE_ANNO_TYPE(TYPE_INT32, statement); + *_retval = statement->AsInt32(kAnnoIndex_Content); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetPageAnnotationInt64(nsIURI* aURI, + const nsACString& aName, + int64_t* _retval) +{ + NS_ENSURE_ARG(aURI); + NS_ENSURE_ARG_POINTER(_retval); + + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartGetAnnotation(aURI, 0, aName, statement); + if (NS_FAILED(rv)) + return rv; + + mozStorageStatementScoper scoper(statement); + ENSURE_ANNO_TYPE(TYPE_INT64, statement); + *_retval = statement->AsInt64(kAnnoIndex_Content); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetItemAnnotationInt64(int64_t aItemId, + const nsACString& aName, + int64_t* _retval) +{ + NS_ENSURE_ARG_MIN(aItemId, 1); + NS_ENSURE_ARG_POINTER(_retval); + + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartGetAnnotation(nullptr, aItemId, aName, statement); + if (NS_FAILED(rv)) + return rv; + + mozStorageStatementScoper scoper(statement); + ENSURE_ANNO_TYPE(TYPE_INT64, statement); + *_retval = statement->AsInt64(kAnnoIndex_Content); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetPageAnnotationType(nsIURI* aURI, + const nsACString& aName, + uint16_t* _retval) +{ + NS_ENSURE_ARG(aURI); + NS_ENSURE_ARG_POINTER(_retval); + + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartGetAnnotation(aURI, 0, aName, statement); + if (NS_FAILED(rv)) + return rv; + + mozStorageStatementScoper scoper(statement); + *_retval = statement->AsInt32(kAnnoIndex_Type); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetItemAnnotationType(int64_t aItemId, + const nsACString& aName, + uint16_t* _retval) +{ + NS_ENSURE_ARG_MIN(aItemId, 1); + NS_ENSURE_ARG_POINTER(_retval); + + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartGetAnnotation(nullptr, aItemId, aName, statement); + if (NS_FAILED(rv)) + return rv; + + mozStorageStatementScoper scoper(statement); + *_retval = statement->AsInt32(kAnnoIndex_Type); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetPageAnnotationDouble(nsIURI* aURI, + const nsACString& aName, + double* _retval) +{ + NS_ENSURE_ARG(aURI); + NS_ENSURE_ARG_POINTER(_retval); + + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartGetAnnotation(aURI, 0, aName, statement); + if (NS_FAILED(rv)) + return rv; + + mozStorageStatementScoper scoper(statement); + ENSURE_ANNO_TYPE(TYPE_DOUBLE, statement); + *_retval = statement->AsDouble(kAnnoIndex_Content); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetItemAnnotationDouble(int64_t aItemId, + const nsACString& aName, + double* _retval) +{ + NS_ENSURE_ARG_MIN(aItemId, 1); + + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartGetAnnotation(nullptr, aItemId, aName, statement); + if (NS_FAILED(rv)) + return rv; + + mozStorageStatementScoper scoper(statement); + ENSURE_ANNO_TYPE(TYPE_DOUBLE, statement); + *_retval = statement->AsDouble(kAnnoIndex_Content); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetPageAnnotationInfo(nsIURI* aURI, + const nsACString& aName, + int32_t* _flags, + uint16_t* _expiration, + uint16_t* _storageType) +{ + NS_ENSURE_ARG(aURI); + NS_ENSURE_ARG_POINTER(_flags); + NS_ENSURE_ARG_POINTER(_expiration); + NS_ENSURE_ARG_POINTER(_storageType); + + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartGetAnnotation(aURI, 0, aName, statement); + if (NS_FAILED(rv)) + return rv; + + mozStorageStatementScoper scoper(statement); + *_flags = statement->AsInt32(kAnnoIndex_Flags); + *_expiration = (uint16_t)statement->AsInt32(kAnnoIndex_Expiration); + int32_t type = (uint16_t)statement->AsInt32(kAnnoIndex_Type); + if (type == 0) { + // For annotations created before explicit typing, + // we can't determine type, just return as string type. + *_storageType = nsIAnnotationService::TYPE_STRING; + } + else + *_storageType = type; + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetItemAnnotationInfo(int64_t aItemId, + const nsACString& aName, + int32_t* _flags, + uint16_t* _expiration, + uint16_t* _storageType) +{ + NS_ENSURE_ARG_MIN(aItemId, 1); + NS_ENSURE_ARG_POINTER(_flags); + NS_ENSURE_ARG_POINTER(_expiration); + NS_ENSURE_ARG_POINTER(_storageType); + + nsCOMPtr<mozIStorageStatement> statement; + nsresult rv = StartGetAnnotation(nullptr, aItemId, aName, statement); + if (NS_FAILED(rv)) + return rv; + + mozStorageStatementScoper scoper(statement); + *_flags = statement->AsInt32(kAnnoIndex_Flags); + *_expiration = (uint16_t)statement->AsInt32(kAnnoIndex_Expiration); + int32_t type = (uint16_t)statement->AsInt32(kAnnoIndex_Type); + if (type == 0) { + // For annotations created before explicit typing, + // we can't determine type, just return as string type. + *_storageType = nsIAnnotationService::TYPE_STRING; + } + else { + *_storageType = type; + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetPagesWithAnnotation(const nsACString& aName, + uint32_t* _resultCount, + nsIURI*** _results) +{ + NS_ENSURE_TRUE(!aName.IsEmpty(), NS_ERROR_INVALID_ARG); + NS_ENSURE_ARG_POINTER(_resultCount); + NS_ENSURE_ARG_POINTER(_results); + + *_resultCount = 0; + *_results = nullptr; + nsCOMArray<nsIURI> results; + + nsresult rv = GetPagesWithAnnotationCOMArray(aName, &results); + NS_ENSURE_SUCCESS(rv, rv); + + // Convert to raw array. + if (results.Count() == 0) + return NS_OK; + + *_resultCount = results.Count(); + results.Forget(_results); + + return NS_OK; +} + + +nsresult +nsAnnotationService::GetPagesWithAnnotationCOMArray(const nsACString& aName, + nsCOMArray<nsIURI>* _results) +{ + nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( + "SELECT h.url " + "FROM moz_anno_attributes n " + "JOIN moz_annos a ON n.id = a.anno_attribute_id " + "JOIN moz_places h ON h.id = a.place_id " + "WHERE n.name = :anno_name" + ); + NS_ENSURE_STATE(stmt); + mozStorageStatementScoper scoper(stmt); + + nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore = false; + while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&hasMore)) && + hasMore) { + nsAutoCString uristring; + rv = stmt->GetUTF8String(0, uristring); + NS_ENSURE_SUCCESS(rv, rv); + + // convert to a URI, in case of some invalid URI, just ignore this row + // so we can mostly continue. + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), uristring); + if (NS_FAILED(rv)) + continue; + + bool added = _results->AppendObject(uri); + NS_ENSURE_TRUE(added, NS_ERROR_OUT_OF_MEMORY); + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetItemsWithAnnotation(const nsACString& aName, + uint32_t* _resultCount, + int64_t** _results) +{ + NS_ENSURE_TRUE(!aName.IsEmpty(), NS_ERROR_INVALID_ARG); + NS_ENSURE_ARG_POINTER(_resultCount); + NS_ENSURE_ARG_POINTER(_results); + + *_resultCount = 0; + *_results = nullptr; + nsTArray<int64_t> results; + + nsresult rv = GetItemsWithAnnotationTArray(aName, &results); + NS_ENSURE_SUCCESS(rv, rv); + + // Convert to raw array. + if (results.Length() == 0) + return NS_OK; + + *_results = static_cast<int64_t*> + (moz_xmalloc(results.Length() * sizeof(int64_t))); + NS_ENSURE_TRUE(*_results, NS_ERROR_OUT_OF_MEMORY); + + *_resultCount = results.Length(); + for (uint32_t i = 0; i < *_resultCount; i ++) { + (*_results)[i] = results[i]; + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetAnnotationsWithName(const nsACString& aName, + uint32_t* _count, + mozIAnnotatedResult*** _annotations) +{ + NS_ENSURE_ARG(!aName.IsEmpty()); + NS_ENSURE_ARG_POINTER(_annotations); + + *_count = 0; + *_annotations = nullptr; + nsCOMArray<mozIAnnotatedResult> annotations; + + nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( + "SELECT h.guid, h.url, -1, a.type, a.content " + "FROM moz_anno_attributes n " + "JOIN moz_annos a ON n.id = a.anno_attribute_id " + "JOIN moz_places h ON h.id = a.place_id " + "WHERE n.name = :anno_name " + "UNION ALL " + "SELECT b.guid, h.url, b.id, a.type, a.content " + "FROM moz_anno_attributes n " + "JOIN moz_items_annos a ON n.id = a.anno_attribute_id " + "JOIN moz_bookmarks b ON b.id = a.item_id " + "LEFT JOIN moz_places h ON h.id = b.fk " + "WHERE n.name = :anno_name " + ); + NS_ENSURE_STATE(stmt); + mozStorageStatementScoper scoper(stmt); + + nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), + aName); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore = false; + while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&hasMore)) && hasMore) { + nsAutoCString guid; + rv = stmt->GetUTF8String(0, guid); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> uri; + bool uriIsNull = false; + rv = stmt->GetIsNull(1, &uriIsNull); + NS_ENSURE_SUCCESS(rv, rv); + if (!uriIsNull) { + nsAutoCString url; + rv = stmt->GetUTF8String(1, url); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_NewURI(getter_AddRefs(uri), url); + NS_ENSURE_SUCCESS(rv, rv); + } + + int64_t itemId = stmt->AsInt64(2); + int32_t type = stmt->AsInt32(3); + + nsCOMPtr<nsIWritableVariant> variant = new nsVariant(); + switch (type) { + case nsIAnnotationService::TYPE_INT32: { + rv = variant->SetAsInt32(stmt->AsInt32(4)); + break; + } + case nsIAnnotationService::TYPE_INT64: { + rv = variant->SetAsInt64(stmt->AsInt64(4)); + break; + } + case nsIAnnotationService::TYPE_DOUBLE: { + rv = variant->SetAsDouble(stmt->AsDouble(4)); + break; + } + case nsIAnnotationService::TYPE_STRING: { + nsAutoString valueString; + rv = stmt->GetString(4, valueString); + NS_ENSURE_SUCCESS(rv, rv); + + rv = variant->SetAsAString(valueString); + break; + } + default: + MOZ_ASSERT(false, "Unsupported annotation type"); + // Move to the next result. + continue; + } + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<mozIAnnotatedResult> anno = new AnnotatedResult(guid, uri, itemId, + aName, variant); + NS_ENSURE_TRUE(annotations.AppendObject(anno), NS_ERROR_OUT_OF_MEMORY); + } + + // Convert to raw array. + if (annotations.Count() == 0) + return NS_OK; + + *_count = annotations.Count(); + annotations.Forget(_annotations); + + return NS_OK; +} + + +nsresult +nsAnnotationService::GetItemsWithAnnotationTArray(const nsACString& aName, + nsTArray<int64_t>* _results) +{ + nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( + "SELECT a.item_id " + "FROM moz_anno_attributes n " + "JOIN moz_items_annos a ON n.id = a.anno_attribute_id " + "WHERE n.name = :anno_name" + ); + NS_ENSURE_STATE(stmt); + mozStorageStatementScoper scoper(stmt); + + nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore = false; + while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && + hasMore) { + if (!_results->AppendElement(stmt->AsInt64(0))) + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetPageAnnotationNames(nsIURI* aURI, + uint32_t* _count, + nsIVariant*** _result) +{ + NS_ENSURE_ARG(aURI); + NS_ENSURE_ARG_POINTER(_count); + NS_ENSURE_ARG_POINTER(_result); + + *_count = 0; + *_result = nullptr; + + nsTArray<nsCString> names; + nsresult rv = GetAnnotationNamesTArray(aURI, 0, &names); + NS_ENSURE_SUCCESS(rv, rv); + + if (names.Length() == 0) + return NS_OK; + + *_result = static_cast<nsIVariant**> + (moz_xmalloc(sizeof(nsIVariant*) * names.Length())); + NS_ENSURE_TRUE(*_result, NS_ERROR_OUT_OF_MEMORY); + + for (uint32_t i = 0; i < names.Length(); i ++) { + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); + if (!var) { + // need to release all the variants we've already created + for (uint32_t j = 0; j < i; j ++) + NS_RELEASE((*_result)[j]); + free(*_result); + *_result = nullptr; + return NS_ERROR_OUT_OF_MEMORY; + } + var->SetAsAUTF8String(names[i]); + NS_ADDREF((*_result)[i] = var); + } + *_count = names.Length(); + + return NS_OK; +} + + +nsresult +nsAnnotationService::GetAnnotationNamesTArray(nsIURI* aURI, + int64_t aItemId, + nsTArray<nsCString>* _result) +{ + _result->Clear(); + + bool isItemAnnotation = (aItemId > 0); + nsCOMPtr<mozIStorageStatement> statement; + if (isItemAnnotation) { + statement = mDB->GetStatement( + "SELECT n.name " + "FROM moz_anno_attributes n " + "JOIN moz_items_annos a ON a.anno_attribute_id = n.id " + "WHERE a.item_id = :item_id" + ); + } + else { + statement = mDB->GetStatement( + "SELECT n.name " + "FROM moz_anno_attributes n " + "JOIN moz_annos a ON a.anno_attribute_id = n.id " + "JOIN moz_places h ON h.id = a.place_id " + "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url" + ); + } + NS_ENSURE_STATE(statement); + mozStorageStatementScoper scoper(statement); + + nsresult rv; + if (isItemAnnotation) + rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId); + else + rv = URIBinder::Bind(statement, NS_LITERAL_CSTRING("page_url"), aURI); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasResult = false; + while (NS_SUCCEEDED(statement->ExecuteStep(&hasResult)) && + hasResult) { + nsAutoCString name; + rv = statement->GetUTF8String(0, name); + NS_ENSURE_SUCCESS(rv, rv); + if (!_result->AppendElement(name)) + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::GetItemAnnotationNames(int64_t aItemId, + uint32_t* _count, + nsIVariant*** _result) +{ + NS_ENSURE_ARG_MIN(aItemId, 1); + NS_ENSURE_ARG_POINTER(_count); + NS_ENSURE_ARG_POINTER(_result); + + *_count = 0; + *_result = nullptr; + + nsTArray<nsCString> names; + nsresult rv = GetAnnotationNamesTArray(nullptr, aItemId, &names); + NS_ENSURE_SUCCESS(rv, rv); + + if (names.Length() == 0) + return NS_OK; + + *_result = static_cast<nsIVariant**> + (moz_xmalloc(sizeof(nsIVariant*) * names.Length())); + NS_ENSURE_TRUE(*_result, NS_ERROR_OUT_OF_MEMORY); + + for (uint32_t i = 0; i < names.Length(); i ++) { + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); + if (!var) { + // need to release all the variants we've already created + for (uint32_t j = 0; j < i; j ++) + NS_RELEASE((*_result)[j]); + free(*_result); + *_result = nullptr; + return NS_ERROR_OUT_OF_MEMORY; + } + var->SetAsAUTF8String(names[i]); + NS_ADDREF((*_result)[i] = var); + } + *_count = names.Length(); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::PageHasAnnotation(nsIURI* aURI, + const nsACString& aName, + bool* _retval) +{ + NS_ENSURE_ARG(aURI); + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv = HasAnnotationInternal(aURI, 0, aName, _retval); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::ItemHasAnnotation(int64_t aItemId, + const nsACString& aName, + bool* _retval) +{ + NS_ENSURE_ARG_MIN(aItemId, 1); + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv = HasAnnotationInternal(nullptr, aItemId, aName, _retval); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +/** + * @note We don't remove anything from the moz_anno_attributes table. If we + * delete the last item of a given name, that item really should go away. + * It will be cleaned up by expiration. + */ +nsresult +nsAnnotationService::RemoveAnnotationInternal(nsIURI* aURI, + int64_t aItemId, + const nsACString& aName) +{ + bool isItemAnnotation = (aItemId > 0); + nsCOMPtr<mozIStorageStatement> statement; + if (isItemAnnotation) { + statement = mDB->GetStatement( + "DELETE FROM moz_items_annos " + "WHERE item_id = :item_id " + "AND anno_attribute_id = " + "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name)" + ); + } + else { + statement = mDB->GetStatement( + "DELETE FROM moz_annos " + "WHERE place_id = " + "(SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) " + "AND anno_attribute_id = " + "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name)" + ); + } + NS_ENSURE_STATE(statement); + mozStorageStatementScoper scoper(statement); + + nsresult rv; + if (isItemAnnotation) + rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId); + else + rv = URIBinder::Bind(statement, NS_LITERAL_CSTRING("page_url"), aURI); + NS_ENSURE_SUCCESS(rv, rv); + + rv = statement->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = statement->Execute(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::RemovePageAnnotation(nsIURI* aURI, + const nsACString& aName) +{ + NS_ENSURE_ARG(aURI); + + nsresult rv = RemoveAnnotationInternal(aURI, 0, aName); + NS_ENSURE_SUCCESS(rv, rv); + + NOTIFY_ANNOS_OBSERVERS(OnPageAnnotationRemoved(aURI, aName)); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::RemoveItemAnnotation(int64_t aItemId, + const nsACString& aName, + uint16_t aSource) +{ + NS_ENSURE_ARG_MIN(aItemId, 1); + + nsresult rv = RemoveAnnotationInternal(nullptr, aItemId, aName); + NS_ENSURE_SUCCESS(rv, rv); + + NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationRemoved(aItemId, aName, aSource)); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::RemovePageAnnotations(nsIURI* aURI) +{ + NS_ENSURE_ARG(aURI); + + // Should this be precompiled or a getter? + nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement( + "DELETE FROM moz_annos WHERE place_id = " + "(SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url)" + ); + NS_ENSURE_STATE(statement); + mozStorageStatementScoper scoper(statement); + + nsresult rv = URIBinder::Bind(statement, NS_LITERAL_CSTRING("page_url"), aURI); + NS_ENSURE_SUCCESS(rv, rv); + + rv = statement->Execute(); + NS_ENSURE_SUCCESS(rv, rv); + + // Update observers + NOTIFY_ANNOS_OBSERVERS(OnPageAnnotationRemoved(aURI, EmptyCString())); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::RemoveItemAnnotations(int64_t aItemId, + uint16_t aSource) +{ + NS_ENSURE_ARG_MIN(aItemId, 1); + + // Should this be precompiled or a getter? + nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement( + "DELETE FROM moz_items_annos WHERE item_id = :item_id" + ); + NS_ENSURE_STATE(statement); + mozStorageStatementScoper scoper(statement); + + nsresult rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId); + NS_ENSURE_SUCCESS(rv, rv); + + rv = statement->Execute(); + NS_ENSURE_SUCCESS(rv, rv); + + NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationRemoved(aItemId, EmptyCString(), + aSource)); + + return NS_OK; +} + + +/** + * @note If we use annotations for some standard items like GeckoFlags, it + * might be a good idea to blacklist these standard annotations from this + * copy function. + */ +NS_IMETHODIMP +nsAnnotationService::CopyPageAnnotations(nsIURI* aSourceURI, + nsIURI* aDestURI, + bool aOverwriteDest) +{ + NS_ENSURE_ARG(aSourceURI); + NS_ENSURE_ARG(aDestURI); + + mozStorageTransaction transaction(mDB->MainConn(), false); + + nsCOMPtr<mozIStorageStatement> sourceStmt = mDB->GetStatement( + "SELECT h.id, n.id, n.name, a2.id " + "FROM moz_places h " + "JOIN moz_annos a ON a.place_id = h.id " + "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id " + "LEFT JOIN moz_annos a2 ON a2.place_id = " + "(SELECT id FROM moz_places WHERE url_hash = hash(:dest_url) AND url = :dest_url) " + "AND a2.anno_attribute_id = n.id " + "WHERE url = :source_url" + ); + NS_ENSURE_STATE(sourceStmt); + mozStorageStatementScoper sourceScoper(sourceStmt); + + nsresult rv = URIBinder::Bind(sourceStmt, NS_LITERAL_CSTRING("source_url"), aSourceURI); + NS_ENSURE_SUCCESS(rv, rv); + rv = URIBinder::Bind(sourceStmt, NS_LITERAL_CSTRING("dest_url"), aDestURI); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<mozIStorageStatement> copyStmt = mDB->GetStatement( + "INSERT INTO moz_annos " + "(place_id, anno_attribute_id, content, flags, expiration, " + "type, dateAdded, lastModified) " + "SELECT (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url), " + "anno_attribute_id, content, flags, expiration, type, " + ":date, :date " + "FROM moz_annos " + "WHERE place_id = :page_id " + "AND anno_attribute_id = :name_id" + ); + NS_ENSURE_STATE(copyStmt); + mozStorageStatementScoper copyScoper(copyStmt); + + bool hasResult; + while (NS_SUCCEEDED(sourceStmt->ExecuteStep(&hasResult)) && hasResult) { + int64_t sourcePlaceId = sourceStmt->AsInt64(0); + int64_t annoNameID = sourceStmt->AsInt64(1); + nsAutoCString annoName; + rv = sourceStmt->GetUTF8String(2, annoName); + NS_ENSURE_SUCCESS(rv, rv); + int64_t annoExistsOnDest = sourceStmt->AsInt64(3); + + if (annoExistsOnDest) { + if (!aOverwriteDest) + continue; + rv = RemovePageAnnotation(aDestURI, annoName); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Copy the annotation. + mozStorageStatementScoper scoper(copyStmt); + rv = URIBinder::Bind(copyStmt, NS_LITERAL_CSTRING("page_url"), aDestURI); + NS_ENSURE_SUCCESS(rv, rv); + rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), sourcePlaceId); + NS_ENSURE_SUCCESS(rv, rv); + rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("name_id"), annoNameID); + NS_ENSURE_SUCCESS(rv, rv); + rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("date"), PR_Now()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = copyStmt->Execute(); + NS_ENSURE_SUCCESS(rv, rv); + + NOTIFY_ANNOS_OBSERVERS(OnPageAnnotationSet(aDestURI, annoName)); + } + + rv = transaction.Commit(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::CopyItemAnnotations(int64_t aSourceItemId, + int64_t aDestItemId, + bool aOverwriteDest, + uint16_t aSource) +{ + NS_ENSURE_ARG_MIN(aSourceItemId, 1); + NS_ENSURE_ARG_MIN(aDestItemId, 1); + + mozStorageTransaction transaction(mDB->MainConn(), false); + + nsCOMPtr<mozIStorageStatement> sourceStmt = mDB->GetStatement( + "SELECT n.id, n.name, a2.id " + "FROM moz_bookmarks b " + "JOIN moz_items_annos a ON a.item_id = b.id " + "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id " + "LEFT JOIN moz_items_annos a2 ON a2.item_id = :dest_item_id " + "AND a2.anno_attribute_id = n.id " + "WHERE b.id = :source_item_id" + ); + NS_ENSURE_STATE(sourceStmt); + mozStorageStatementScoper sourceScoper(sourceStmt); + + nsresult rv = sourceStmt->BindInt64ByName(NS_LITERAL_CSTRING("source_item_id"), aSourceItemId); + NS_ENSURE_SUCCESS(rv, rv); + rv = sourceStmt->BindInt64ByName(NS_LITERAL_CSTRING("dest_item_id"), aDestItemId); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<mozIStorageStatement> copyStmt = mDB->GetStatement( + "INSERT OR REPLACE INTO moz_items_annos " + "(item_id, anno_attribute_id, content, flags, expiration, " + "type, dateAdded, lastModified) " + "SELECT :dest_item_id, anno_attribute_id, content, flags, expiration, " + "type, :date, :date " + "FROM moz_items_annos " + "WHERE item_id = :source_item_id " + "AND anno_attribute_id = :name_id" + ); + NS_ENSURE_STATE(copyStmt); + mozStorageStatementScoper copyScoper(copyStmt); + + bool hasResult; + while (NS_SUCCEEDED(sourceStmt->ExecuteStep(&hasResult)) && hasResult) { + int64_t annoNameID = sourceStmt->AsInt64(0); + nsAutoCString annoName; + rv = sourceStmt->GetUTF8String(1, annoName); + NS_ENSURE_SUCCESS(rv, rv); + int64_t annoExistsOnDest = sourceStmt->AsInt64(2); + + if (annoExistsOnDest) { + if (!aOverwriteDest) + continue; + rv = RemoveItemAnnotation(aDestItemId, annoName, aSource); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Copy the annotation. + mozStorageStatementScoper scoper(copyStmt); + rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("dest_item_id"), aDestItemId); + NS_ENSURE_SUCCESS(rv, rv); + rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("source_item_id"), aSourceItemId); + NS_ENSURE_SUCCESS(rv, rv); + rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("name_id"), annoNameID); + NS_ENSURE_SUCCESS(rv, rv); + rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("date"), PR_Now()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = copyStmt->Execute(); + NS_ENSURE_SUCCESS(rv, rv); + + NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aDestItemId, annoName, aSource)); + } + + rv = transaction.Commit(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::AddObserver(nsIAnnotationObserver* aObserver) +{ + NS_ENSURE_ARG(aObserver); + + if (mObservers.IndexOfObject(aObserver) >= 0) + return NS_ERROR_INVALID_ARG; // Already registered. + if (!mObservers.AppendObject(aObserver)) + return NS_ERROR_OUT_OF_MEMORY; + return NS_OK; +} + + +NS_IMETHODIMP +nsAnnotationService::RemoveObserver(nsIAnnotationObserver* aObserver) +{ + NS_ENSURE_ARG(aObserver); + + if (!mObservers.RemoveObject(aObserver)) + return NS_ERROR_INVALID_ARG; + return NS_OK; +} + +nsresult +nsAnnotationService::HasAnnotationInternal(nsIURI* aURI, + int64_t aItemId, + const nsACString& aName, + bool* _hasAnno) +{ + bool isItemAnnotation = (aItemId > 0); + nsCOMPtr<mozIStorageStatement> stmt; + if (isItemAnnotation) { + stmt = mDB->GetStatement( + "SELECT b.id, " + "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS nameid, " + "a.id, a.dateAdded " + "FROM moz_bookmarks b " + "LEFT JOIN moz_items_annos a ON a.item_id = b.id " + "AND a.anno_attribute_id = nameid " + "WHERE b.id = :item_id" + ); + } + else { + stmt = mDB->GetStatement( + "SELECT h.id, " + "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS nameid, " + "a.id, a.dateAdded " + "FROM moz_places h " + "LEFT JOIN moz_annos a ON a.place_id = h.id " + "AND a.anno_attribute_id = nameid " + "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url" + ); + } + NS_ENSURE_STATE(stmt); + mozStorageStatementScoper checkAnnoScoper(stmt); + + nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName); + NS_ENSURE_SUCCESS(rv, rv); + if (isItemAnnotation) + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId); + else + rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasResult; + rv = stmt->ExecuteStep(&hasResult); + NS_ENSURE_SUCCESS(rv, rv); + if (!hasResult) { + // We are trying to get an annotation on an invalid bookmarks or + // history entry. + // Here we preserve the old behavior, returning that we don't have the + // annotation, ignoring the fact itemId is invalid. + // Otherwise we should return NS_ERROR_INVALID_ARG, but this will somehow + // break the API. In future we could want to be pickier. + *_hasAnno = false; + } + else { + int64_t annotationId = stmt->AsInt64(2); + *_hasAnno = (annotationId > 0); + } + + return NS_OK; +} + + +/** + * This loads the statement and steps it once so you can get data out of it. + * + * @note You have to reset the statement when you're done if this succeeds. + * @throws NS_ERROR_NOT_AVAILABLE if the annotation is not found. + */ + +nsresult +nsAnnotationService::StartGetAnnotation(nsIURI* aURI, + int64_t aItemId, + const nsACString& aName, + nsCOMPtr<mozIStorageStatement>& aStatement) +{ + bool isItemAnnotation = (aItemId > 0); + + if (isItemAnnotation) { + aStatement = mDB->GetStatement( + "SELECT a.id, a.item_id, :anno_name, a.content, a.flags, " + "a.expiration, a.type " + "FROM moz_anno_attributes n " + "JOIN moz_items_annos a ON a.anno_attribute_id = n.id " + "WHERE a.item_id = :item_id " + "AND n.name = :anno_name" + ); + } + else { + aStatement = mDB->GetStatement( + "SELECT a.id, a.place_id, :anno_name, a.content, a.flags, " + "a.expiration, a.type " + "FROM moz_anno_attributes n " + "JOIN moz_annos a ON n.id = a.anno_attribute_id " + "JOIN moz_places h ON h.id = a.place_id " + "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url " + "AND n.name = :anno_name" + ); + } + NS_ENSURE_STATE(aStatement); + mozStorageStatementScoper getAnnoScoper(aStatement); + + nsresult rv; + if (isItemAnnotation) + rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId); + else + rv = URIBinder::Bind(aStatement, NS_LITERAL_CSTRING("page_url"), aURI); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aStatement->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasResult = false; + rv = aStatement->ExecuteStep(&hasResult); + if (NS_FAILED(rv) || !hasResult) + return NS_ERROR_NOT_AVAILABLE; + + // on success, DON'T reset the statement, the caller needs to read from it, + // and it is the caller's job to reset it. + getAnnoScoper.Abandon(); + + return NS_OK; +} + + +/** + * This does most of the setup work needed to set an annotation, except for + * binding the the actual value and executing the statement. + * It will either update an existing annotation or insert a new one. + * + * @note The aStatement RESULT IS NOT ADDREFED. This is just one of the class + * vars, which control its scope. DO NOT RELEASE. + * The caller must take care of resetting the statement if this succeeds. + */ +nsresult +nsAnnotationService::StartSetAnnotation(nsIURI* aURI, + int64_t aItemId, + const nsACString& aName, + int32_t aFlags, + uint16_t aExpiration, + uint16_t aType, + nsCOMPtr<mozIStorageStatement>& aStatement) +{ + bool isItemAnnotation = (aItemId > 0); + + if (aExpiration == EXPIRE_SESSION) { + mHasSessionAnnotations = true; + } + + // Ensure the annotation name exists. + nsCOMPtr<mozIStorageStatement> addNameStmt = mDB->GetStatement( + "INSERT OR IGNORE INTO moz_anno_attributes (name) VALUES (:anno_name)" + ); + NS_ENSURE_STATE(addNameStmt); + mozStorageStatementScoper scoper(addNameStmt); + + nsresult rv = addNameStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName); + NS_ENSURE_SUCCESS(rv, rv); + rv = addNameStmt->Execute(); + NS_ENSURE_SUCCESS(rv, rv); + + // We have to check 2 things: + // - if the annotation already exists we should update it. + // - we should not allow setting annotations on invalid URIs or itemIds. + // This query will tell us: + // - whether the item or page exists. + // - whether the annotation already exists. + // - the nameID associated with the annotation name. + // - the id and dateAdded of the old annotation, if it exists. + nsCOMPtr<mozIStorageStatement> stmt; + if (isItemAnnotation) { + stmt = mDB->GetStatement( + "SELECT b.id, " + "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS nameid, " + "a.id, a.dateAdded " + "FROM moz_bookmarks b " + "LEFT JOIN moz_items_annos a ON a.item_id = b.id " + "AND a.anno_attribute_id = nameid " + "WHERE b.id = :item_id" + ); + } + else { + stmt = mDB->GetStatement( + "SELECT h.id, " + "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS nameid, " + "a.id, a.dateAdded " + "FROM moz_places h " + "LEFT JOIN moz_annos a ON a.place_id = h.id " + "AND a.anno_attribute_id = nameid " + "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url" + ); + } + NS_ENSURE_STATE(stmt); + mozStorageStatementScoper checkAnnoScoper(stmt); + + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName); + NS_ENSURE_SUCCESS(rv, rv); + if (isItemAnnotation) + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId); + else + rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasResult; + rv = stmt->ExecuteStep(&hasResult); + NS_ENSURE_SUCCESS(rv, rv); + if (!hasResult) { + // We are trying to create an annotation on an invalid bookmark + // or history entry. + return NS_ERROR_INVALID_ARG; + } + + int64_t fkId = stmt->AsInt64(0); + int64_t nameID = stmt->AsInt64(1); + int64_t oldAnnoId = stmt->AsInt64(2); + int64_t oldAnnoDate = stmt->AsInt64(3); + + if (isItemAnnotation) { + aStatement = mDB->GetStatement( + "INSERT OR REPLACE INTO moz_items_annos " + "(id, item_id, anno_attribute_id, content, flags, " + "expiration, type, dateAdded, lastModified) " + "VALUES (:id, :fk, :name_id, :content, :flags, " + ":expiration, :type, :date_added, :last_modified)" + ); + } + else { + aStatement = mDB->GetStatement( + "INSERT OR REPLACE INTO moz_annos " + "(id, place_id, anno_attribute_id, content, flags, " + "expiration, type, dateAdded, lastModified) " + "VALUES (:id, :fk, :name_id, :content, :flags, " + ":expiration, :type, :date_added, :last_modified)" + ); + } + NS_ENSURE_STATE(aStatement); + mozStorageStatementScoper setAnnoScoper(aStatement); + + // Don't replace existing annotations. + if (oldAnnoId > 0) { + rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), oldAnnoId); + NS_ENSURE_SUCCESS(rv, rv); + rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), oldAnnoDate); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + rv = aStatement->BindNullByName(NS_LITERAL_CSTRING("id")); + NS_ENSURE_SUCCESS(rv, rv); + rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), RoundedPRNow()); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("fk"), fkId); + NS_ENSURE_SUCCESS(rv, rv); + rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("name_id"), nameID); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aStatement->BindInt32ByName(NS_LITERAL_CSTRING("flags"), aFlags); + NS_ENSURE_SUCCESS(rv, rv); + rv = aStatement->BindInt32ByName(NS_LITERAL_CSTRING("expiration"), aExpiration); + NS_ENSURE_SUCCESS(rv, rv); + rv = aStatement->BindInt32ByName(NS_LITERAL_CSTRING("type"), aType); + NS_ENSURE_SUCCESS(rv, rv); + rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), RoundedPRNow()); + NS_ENSURE_SUCCESS(rv, rv); + + // On success, leave the statement open, the caller will set the value + // and execute the statement. + setAnnoScoper.Abandon(); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// nsIObserver + +NS_IMETHODIMP +nsAnnotationService::Observe(nsISupports *aSubject, + const char *aTopic, + const char16_t *aData) +{ + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); + + if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) { + // Remove all session annotations, if any. + if (mHasSessionAnnotations) { + nsCOMPtr<mozIStorageAsyncStatement> pageAnnoStmt = mDB->GetAsyncStatement( + "DELETE FROM moz_annos WHERE expiration = :expire_session" + ); + NS_ENSURE_STATE(pageAnnoStmt); + nsresult rv = pageAnnoStmt->BindInt32ByName(NS_LITERAL_CSTRING("expire_session"), + EXPIRE_SESSION); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<mozIStorageAsyncStatement> itemAnnoStmt = mDB->GetAsyncStatement( + "DELETE FROM moz_items_annos WHERE expiration = :expire_session" + ); + NS_ENSURE_STATE(itemAnnoStmt); + rv = itemAnnoStmt->BindInt32ByName(NS_LITERAL_CSTRING("expire_session"), + EXPIRE_SESSION); + NS_ENSURE_SUCCESS(rv, rv); + + mozIStorageBaseStatement *stmts[] = { + pageAnnoStmt.get() + , itemAnnoStmt.get() + }; + + nsCOMPtr<mozIStoragePendingStatement> ps; + rv = mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts), nullptr, + getter_AddRefs(ps)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} |