/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4; c-file-style: "stroustrup" -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* The global bookmarks service. */ #include "nsCOMPtr.h" #include "nsFileSpec.h" #include "nsFileStream.h" #include "nsIBookmarksService.h" #include "nsIComponentManager.h" #include "nsIGenericFactory.h" #include "nsIRDFContainer.h" #include "nsIRDFContainerUtils.h" #include "nsIRDFDataSource.h" #include "nsIRDFNode.h" #include "nsIRDFService.h" #include "nsIServiceManager.h" #include "nsRDFCID.h" #include "nsSpecialSystemDirectory.h" #include "nsString.h" #include "nsVoidArray.h" #include "nsXPIDLString.h" #include "prio.h" #include "prlog.h" #include "rdf.h" #include "xp_core.h" //////////////////////////////////////////////////////////////////////// static NS_DEFINE_CID(kBookmarksServiceCID, NS_BOOKMARKS_SERVICE_CID); static NS_DEFINE_CID(kComponentManagerCID, NS_COMPONENTMANAGER_CID); static NS_DEFINE_CID(kGenericFactoryCID, NS_GENERICFACTORY_CID); static NS_DEFINE_CID(kRDFInMemoryDataSourceCID, NS_RDFINMEMORYDATASOURCE_CID); static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); static NS_DEFINE_CID(kRDFContainerCID, NS_RDFCONTAINER_CID); static NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID); static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); static const char kURINC_BookmarksRoot[] = "NC:BookmarksRoot"; // XXX? static const char kURINC_IEFavoritesRoot[] = "NC:IEFavoritesRoot"; // XXX? static const char kURINC_PersonalToolbarFolder[] = "NC:PersonalToolbarFolder"; // XXX? static const char kPersonalToolbarFolder[] = "Personal Toolbar Folder"; //////////////////////////////////////////////////////////////////////// PRInt32 gRefCnt; nsIRDFService* gRDF; nsIRDFContainerUtils* gRDFC; nsIRDFResource* kNC_Bookmark; nsIRDFResource* kNC_BookmarkSeparator; nsIRDFResource* kNC_BookmarkAddDate; nsIRDFResource* kNC_BookmarksRoot; nsIRDFResource* kNC_Description; nsIRDFResource* kNC_Folder; nsIRDFResource* kNC_IEFavorite; nsIRDFResource* kNC_IEFavoritesRoot; nsIRDFResource* kNC_Name; nsIRDFResource* kNC_PersonalToolbarFolder; nsIRDFResource* kNC_ShortcutURL; nsIRDFResource* kNC_URL; nsIRDFResource* kRDF_type; nsIRDFResource* kWEB_LastModifiedDate; nsIRDFResource* kWEB_LastVisitDate; static nsresult bm_AddRefGlobals() { if (gRefCnt++ == 0) { nsresult rv; rv = nsServiceManager::GetService(kRDFServiceCID, nsIRDFService::GetIID(), (nsISupports**) &gRDF); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service"); if (NS_FAILED(rv)) return rv; rv = nsServiceManager::GetService(kRDFContainerUtilsCID, nsIRDFContainerUtils::GetIID(), (nsISupports**) &gRDFC); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF container utils"); if (NS_FAILED(rv)) return rv; gRDF->GetResource(NC_NAMESPACE_URI "Bookmark", &kNC_Bookmark); gRDF->GetResource(NC_NAMESPACE_URI "BookmarkSeparator", &kNC_BookmarkSeparator); gRDF->GetResource(NC_NAMESPACE_URI "BookmarkAddDate", &kNC_BookmarkAddDate); gRDF->GetResource(NC_NAMESPACE_URI "BookmarksRoot", &kNC_BookmarksRoot); gRDF->GetResource(NC_NAMESPACE_URI "Description", &kNC_Description); gRDF->GetResource(NC_NAMESPACE_URI "Folder", &kNC_Folder); gRDF->GetResource(NC_NAMESPACE_URI "IEFavorite", &kNC_IEFavorite); gRDF->GetResource(NC_NAMESPACE_URI "IEFavoritesRoot", &kNC_IEFavoritesRoot); gRDF->GetResource(NC_NAMESPACE_URI "Name", &kNC_Name); gRDF->GetResource(NC_NAMESPACE_URI "PersonalToolbarFolder", &kNC_PersonalToolbarFolder); gRDF->GetResource(NC_NAMESPACE_URI "ShortcutURL", &kNC_ShortcutURL); gRDF->GetResource(NC_NAMESPACE_URI "URL", &kNC_URL); gRDF->GetResource(RDF_NAMESPACE_URI "type", &kRDF_type); gRDF->GetResource(WEB_NAMESPACE_URI "LastModifiedDate", &kWEB_LastModifiedDate); gRDF->GetResource(WEB_NAMESPACE_URI "LastVisitDate", &kWEB_LastVisitDate); } return NS_OK; } static void bm_ReleaseGlobals() { if (--gRefCnt == 0) { if (gRDF) nsServiceManager::ReleaseService(kRDFServiceCID, gRDF); if (gRDFC) nsServiceManager::ReleaseService(kRDFContainerUtilsCID, gRDFC); NS_IF_RELEASE(kNC_Bookmark); NS_IF_RELEASE(kNC_BookmarkSeparator); NS_IF_RELEASE(kNC_BookmarkAddDate); NS_IF_RELEASE(kNC_BookmarksRoot); NS_IF_RELEASE(kNC_Description); NS_IF_RELEASE(kNC_Folder); NS_IF_RELEASE(kNC_IEFavorite); NS_IF_RELEASE(kNC_IEFavoritesRoot); NS_IF_RELEASE(kNC_Name); NS_IF_RELEASE(kNC_PersonalToolbarFolder); NS_IF_RELEASE(kNC_ShortcutURL); NS_IF_RELEASE(kNC_URL); NS_IF_RELEASE(kRDF_type); NS_IF_RELEASE(kWEB_LastModifiedDate); NS_IF_RELEASE(kWEB_LastVisitDate); } } //////////////////////////////////////////////////////////////////////// /** * The bookmark parser knows how to read bookmarks.html and convert it * into an RDF graph. */ class BookmarkParser { private: nsInputFileStream *mStream; nsIRDFDataSource *mDataSource; const char *mIEFavoritesRoot; PRBool mFoundIEFavoritesRoot; protected: nsresult AssertTime(nsIRDFResource* aSource, nsIRDFResource* aLabel, PRInt32 aTime); nsresult CreateAnonymousResource(nsCOMPtr* aResult); nsresult ParseBookmark(const nsString& aLine, nsIRDFResource* aContainer, nsIRDFResource *nodeType); nsresult ParseBookmarkHeader(const nsString& aLine, nsIRDFResource* aContainer, nsIRDFResource *nodeType); nsresult ParseBookmarkSeparator(const nsString& aLine, nsIRDFResource* aContainer); nsresult ParseHeaderBegin(const nsString& aLine, nsIRDFResource* aContainer); nsresult ParseHeaderEnd(const nsString& aLine); nsresult ParseAttribute(const nsString& aLine, const char* aAttribute, PRInt32 aAttributeLen, nsString& aResult); public: BookmarkParser(); ~BookmarkParser(); nsresult Init(nsInputFileStream *aStream, nsIRDFDataSource *aDataSource); nsresult Parse(nsIRDFResource* aContainer, nsIRDFResource *nodeType); nsresult AddBookmark(nsIRDFResource* aContainer, const char* aURL, const PRUnichar* aOptionalTitle, PRInt32 aAddDate, PRInt32 aLastVisitDate, PRInt32 aLastModifiedDate, const char* aShortcutURL, nsIRDFResource* aNodeType); nsresult SetIEFavoritesRoot(const char *IEFavoritesRootURL) { mIEFavoritesRoot = IEFavoritesRootURL; return(NS_OK); } nsresult ParserFoundIEFavoritesRoot(PRBool *foundIEFavoritesRoot) { *foundIEFavoritesRoot = mFoundIEFavoritesRoot; return(NS_OK); } }; BookmarkParser::BookmarkParser() { bm_AddRefGlobals(); } nsresult BookmarkParser::Init(nsInputFileStream *aStream, nsIRDFDataSource *aDataSource) { mStream = aStream; mDataSource = aDataSource; mIEFavoritesRoot = nsnull; mFoundIEFavoritesRoot = PR_FALSE; return(NS_OK); } BookmarkParser::~BookmarkParser() { bm_ReleaseGlobals(); } static const char kHREFEquals[] = "HREF=\""; static const char kCloseAnchor[] = ""; static const char kOpenHeading[] = "= 0) { rv = ParseBookmark(line, aContainer, nodeType); } else if ((offset = line.Find(kOpenHeading)) >= 0 && nsString::IsDigit(line.CharAt(offset + 2))) { // XXX Ignore

so that bookmarks root _is_

if (line.CharAt(offset + 2) != PRUnichar('1')) rv = ParseBookmarkHeader(line, aContainer, nodeType); } else if ((offset = line.Find(kSeparator)) >= 0) { rv = ParseBookmarkSeparator(line, aContainer); } else if ((offset = line.Find(kCloseUL)) >= 0 || (offset = line.Find(kCloseMenu)) >= 0 || (offset = line.Find(kCloseDL)) >= 0) { return ParseHeaderEnd(line); } else if ((offset = line.Find(kOpenUL)) >= 0 || (offset = line.Find(kOpenMenu)) >= 0 || (offset = line.Find(kOpenDL)) >= 0) { rv = ParseHeaderBegin(line, aContainer); } else { // XXX Discard the line. We should be treating this as the // description. } } return rv; } nsresult BookmarkParser::CreateAnonymousResource(nsCOMPtr* aResult) { static PRInt32 gNext = 0; if (! gNext) { LL_L2I(gNext, PR_Now()); } nsAutoString uri(kURINC_BookmarksRoot); uri.Append("$"); uri.Append(gNext, 16); return gRDF->GetUnicodeResource(uri.GetUnicode(), getter_AddRefs(*aResult)); } nsresult BookmarkParser::ParseBookmark(const nsString& aLine, nsIRDFResource* aContainer, nsIRDFResource *nodeType) { NS_PRECONDITION(aContainer != nsnull, "null ptr"); if (! aContainer) return NS_ERROR_NULL_POINTER; PRInt32 start = aLine.Find(kHREFEquals); NS_ASSERTION(start >= 0, "no 'HREF=\"' string: how'd we get here?"); if (start < 0) return NS_ERROR_UNEXPECTED; // 1. Crop out the URL // Skip past the first double-quote start += (sizeof(kHREFEquals) - 1); // ...and find the next so we can chop the URL. PRInt32 end = aLine.Find(PRUnichar('"'), start); NS_ASSERTION(end >= 0, "unterminated string"); if (end < 0) return NS_ERROR_UNEXPECTED; nsAutoString url; aLine.Mid(url, start, end - start); { // Now do properly replace %22's (this is what 4.5 did, anyway...) static const char kEscape22[] = "%22"; PRInt32 offset; while ((offset = url.Find(kEscape22)) >= 0) { url.SetCharAt(' ',offset); url.Cut(offset + 1, sizeof(kEscape22) - 2); } } // XXX At this point, the URL may be relative. 4.5 called into // netlib to make an absolute URL, and there was some magic // "relative_URL" parameter that got sent down as well. We punt on // that stuff. // 2. Parse the name start = aLine.Find(PRUnichar('>'), end + 1); // 'end' still points to the end of the URL NS_ASSERTION(start >= 0, "open anchor tag not terminated"); if (start < 0) return NS_ERROR_UNEXPECTED; nsAutoString name; aLine.Right(name, aLine.Length() - (start + 1)); end = name.Find(kCloseAnchor); NS_ASSERTION(end >= 0, "anchor tag not terminated"); if (end < 0) return NS_ERROR_UNEXPECTED; name.Truncate(end); // 3. Parse the target nsAutoString target; start = aLine.Find(kTargetEquals); if (start >= 0) { start += (sizeof(kTargetEquals) - 1); end = aLine.Find(PRUnichar('"'), start); aLine.Mid(target, start, end - start); } // 4. Parse the addition date PRInt32 addDate = 0; { nsAutoString s; ParseAttribute(aLine, kAddDateEquals, sizeof(kAddDateEquals) - 1, s); if (s.Length() > 0) { PRInt32 err; addDate = s.ToInteger(&err); // ignored. } } // 5. Parse the last visit date PRInt32 lastVisitDate = 0; { nsAutoString s; ParseAttribute(aLine, kLastVisitEquals, sizeof(kLastVisitEquals) - 1, s); if (s.Length() > 0) { PRInt32 err; lastVisitDate = s.ToInteger(&err); // ignored. } } // 6. Parse the last modified date PRInt32 lastModifiedDate; { nsAutoString s; ParseAttribute(aLine, kLastModifiedEquals, sizeof(kLastModifiedEquals) - 1, s); if (s.Length() > 0) { PRInt32 err; lastModifiedDate = s.ToInteger(&err); // ignored. } } // 7. Parse the shortcut URL nsAutoString shortcut; ParseAttribute(aLine, kShortcutURLEquals, sizeof(kShortcutURLEquals) -1, shortcut); // Dunno. 4.5 did it, so will we. if (!lastModifiedDate) lastModifiedDate = lastVisitDate; // XXX There was some other cruft here to deal with aliases, but // since I have no clue what those are, I'll punt. char *cURL = url.ToNewCString(); if (cURL) { char *cShortcutURL = shortcut.ToNewCString(); if (! cShortcutURL) return NS_ERROR_OUT_OF_MEMORY; nsresult rv = AddBookmark(aContainer, cURL, name.GetUnicode(), addDate, lastVisitDate, lastModifiedDate, cShortcutURL, nodeType); delete [] cURL; if (cShortcutURL) delete [] cShortcutURL; } return(NS_OK); } // Now create the bookmark nsresult BookmarkParser::AddBookmark(nsIRDFResource* aContainer, const char* aURL, const PRUnichar* aOptionalTitle, PRInt32 aAddDate, PRInt32 aLastVisitDate, PRInt32 aLastModifiedDate, const char* aShortcutURL, nsIRDFResource* aNodeType) { nsresult rv; nsCOMPtr bookmark; if (NS_FAILED(rv = gRDF->GetResource(aURL, getter_AddRefs(bookmark) ))) { NS_ERROR("unable to get bookmark resource"); return rv; } PRBool result = PR_FALSE; if (nsnull != mIEFavoritesRoot) { if (!PL_strcmp(aURL, mIEFavoritesRoot)) { mFoundIEFavoritesRoot = PR_TRUE; } } nsCOMPtr container; rv = nsComponentManager::CreateInstance(kRDFContainerCID, nsnull, nsIRDFContainer::GetIID(), getter_AddRefs(container)); if (NS_FAILED(rv)) return rv; rv = container->AppendElement(bookmark); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to add bookmark to container"); if (NS_FAILED(rv)) return rv; rv = mDataSource->Assert(bookmark, kRDF_type, aNodeType, PR_TRUE); if (rv != NS_RDF_ASSERTION_ACCEPTED) { NS_ERROR("unable to add bookmark to data source"); return rv; } if ((nsnull != aOptionalTitle) && (*aOptionalTitle != PRUnichar('\0'))) { nsCOMPtr literal; if (NS_FAILED(rv = gRDF->GetLiteral(aOptionalTitle, getter_AddRefs(literal)))) { NS_ERROR("unable to create literal for bookmark name"); return rv; } rv = mDataSource->Assert(bookmark, kNC_Name, literal, PR_TRUE); if (rv != NS_RDF_ASSERTION_ACCEPTED) { NS_ERROR("unable to set bookmark name"); return rv; } } AssertTime(bookmark, kNC_BookmarkAddDate, aAddDate); AssertTime(bookmark, kWEB_LastVisitDate, aLastVisitDate); AssertTime(bookmark, kWEB_LastModifiedDate, aLastModifiedDate); if ((nsnull != aShortcutURL) && (*aShortcutURL != '\0')) { nsCOMPtr shortcutLiteral; if (NS_FAILED(rv = gRDF->GetLiteral(nsAutoString(aShortcutURL).GetUnicode(), getter_AddRefs(shortcutLiteral)))) { NS_ERROR("unable to get literal for bookmark shortcut URL"); return(rv); } if (rv != NS_RDF_NO_VALUE) { rv = mDataSource->Assert(bookmark, kNC_ShortcutURL, shortcutLiteral, PR_TRUE); if (rv != NS_RDF_ASSERTION_ACCEPTED) { NS_ERROR("unable to set bookmark shortcut URL"); return(rv); } } } return(NS_OK); } nsresult BookmarkParser::ParseBookmarkHeader(const nsString& aLine, nsIRDFResource* aContainer, nsIRDFResource *nodeType) { // Snip out the header PRInt32 start = aLine.Find(kOpenHeading); NS_ASSERTION(start >= 0, "couldn't find ''), start); // skip to the end of the '' tag if (start < 0) { NS_WARNING("couldn't find end of header tag"); return NS_OK; } nsAutoString name; aLine.Right(name, aLine.Length() - (start + 1)); PRInt32 end = name.Find(kCloseHeading); if (end < 0) NS_WARNING("No '= 0) name.Truncate(end); // Find the add date PRInt32 addDate = 0; nsAutoString s; ParseAttribute(aLine, kAddDateEquals, sizeof(kAddDateEquals) - 1, s); if (s.Length() > 0) { PRInt32 err; addDate = s.ToInteger(&err); // ignored } nsAutoString id; ParseAttribute(aLine, kIDEquals, sizeof(kIDEquals) - 1, id); // Make the necessary assertions nsresult rv; nsCOMPtr folder; if (id.Length() > 0) { // Use the ID attribute, if one is set. rv = gRDF->GetUnicodeResource(id.GetUnicode(), getter_AddRefs(folder)); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create resource for folder"); if (NS_FAILED(rv)) return rv; } else if (name.Equals(kPersonalToolbarFolder)) { // XXX I18n!!! folder = dont_QueryInterface( kNC_PersonalToolbarFolder ); } else { // We've never seen this folder before. Assign it an anonymous ID rv = CreateAnonymousResource(&folder); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create anonymous resource for folder"); if (NS_FAILED(rv)) return rv; } nsCOMPtr literal; rv = gRDF->GetLiteral(name.GetUnicode(), getter_AddRefs(literal)); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create literal for folder name"); if (NS_FAILED(rv)) return rv; rv = mDataSource->Assert(folder, kNC_Name, literal, PR_TRUE); if (rv != NS_RDF_ASSERTION_ACCEPTED) { NS_ERROR("unable to set folder name"); return rv; } nsCOMPtr container; rv = nsComponentManager::CreateInstance(kRDFContainerCID, nsnull, nsIRDFContainer::GetIID(), getter_AddRefs(container)); if (NS_FAILED(rv)) return rv; rv = container->AppendElement(folder); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to add bookmark to container"); if (NS_FAILED(rv)) return rv; rv = gRDFC->MakeSeq(mDataSource, folder, nsnull); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to make new folder as sequence"); if (NS_FAILED(rv)) return rv; rv = mDataSource->Assert(folder, kRDF_type, kNC_Folder, PR_TRUE); if (rv != NS_RDF_ASSERTION_ACCEPTED) { NS_ERROR("unable to mark new folder as folder"); return rv; } if (NS_FAILED(rv = AssertTime(folder, kNC_BookmarkAddDate, addDate))) { NS_ERROR("unable to mark add date"); return rv; } // And now recursively parse the rest of the file... if (NS_FAILED(rv = Parse(folder, nodeType))) { NS_ERROR("recursive parse of bookmarks file failed"); return rv; } return NS_OK; } nsresult BookmarkParser::ParseBookmarkSeparator(const nsString& aLine, nsIRDFResource* aContainer) { nsresult rv; nsCOMPtr separator; if (NS_SUCCEEDED(rv = CreateAnonymousResource(&separator))) { nsAutoString defaultSeparatorName("-----"); nsCOMPtr nameLiteral; if (NS_SUCCEEDED(rv = gRDF->GetLiteral(defaultSeparatorName.GetUnicode(), getter_AddRefs(nameLiteral)))) { if (NS_SUCCEEDED(rv = mDataSource->Assert(separator, kNC_Name, nameLiteral, PR_TRUE))) { } } if (NS_SUCCEEDED(rv = mDataSource->Assert(separator, kRDF_type, kNC_BookmarkSeparator, PR_TRUE))) { nsCOMPtr container; rv = nsComponentManager::CreateInstance(kRDFContainerCID, nsnull, nsIRDFContainer::GetIID(), getter_AddRefs(container)); if (NS_SUCCEEDED(rv)) { rv = container->AppendElement(separator); } } } return(rv); } nsresult BookmarkParser::ParseHeaderBegin(const nsString& aLine, nsIRDFResource* aContainer) { return NS_OK; } nsresult BookmarkParser::ParseHeaderEnd(const nsString& aLine) { return NS_OK; } nsresult BookmarkParser::ParseAttribute(const nsString& aLine, const char* aAttributeName, PRInt32 aAttributeLen, nsString& aResult) { aResult.Truncate(); PRInt32 start = aLine.Find(aAttributeName); if (start < 0) return NS_OK; start += aAttributeLen; PRInt32 end = aLine.Find(PRUnichar('"'), start); aLine.Mid(aResult, start, end - start); return NS_OK; } nsresult BookmarkParser::AssertTime(nsIRDFResource* aSource, nsIRDFResource* aLabel, PRInt32 aTime) { // XXX TO DO: Convert to a date literal nsAutoString time; time.Append(aTime, 10); nsresult rv; nsIRDFLiteral* literal; if (NS_FAILED(rv = gRDF->GetLiteral(time.GetUnicode(), &literal))) { NS_ERROR("unable to get literal for time"); return rv; } rv = mDataSource->Assert(aSource, aLabel, literal, PR_TRUE); NS_ASSERTION(rv == NS_RDF_ASSERTION_ACCEPTED, "unable to assert time"); NS_RELEASE(literal); return rv; } //////////////////////////////////////////////////////////////////////// // BookmarkDataSourceImpl class nsBookmarksService : public nsIBookmarksService, public nsIRDFDataSource { protected: nsIRDFDataSource* mInner; nsresult ReadBookmarks(); nsresult WriteBookmarks(nsIRDFDataSource *ds, nsIRDFResource *root); nsresult WriteBookmarksContainer(nsIRDFDataSource *ds, nsOutputFileStream strm, nsIRDFResource *container, PRInt32 level); nsresult WriteBookmarkProperties(nsIRDFDataSource *ds, nsOutputFileStream strm, nsIRDFResource *node, nsIRDFResource *property, const char *htmlAttrib, PRBool isFirst); PRBool CanAccept(nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aTarget); nsBookmarksService(); virtual ~nsBookmarksService(); nsresult Init(); friend NS_IMETHODIMP NS_NewBookmarksService(nsISupports* aOuter, REFNSIID aIID, void** aResult); public: // nsISupports NS_DECL_ISUPPORTS // nsIRDFBookmarkDataSource NS_IMETHOD AddBookmark(const char *aURI, const PRUnichar *aOptionalTitle); NS_IMETHOD FindShortcut(const PRUnichar *aUserInput, char **aShortcutURL); // nsIRDFDataSource NS_IMETHOD Init(const char* uri) { return mInner->Init(uri); } NS_IMETHOD GetURI(char* *uri) { return mInner->GetURI(uri); } NS_IMETHOD GetSource(nsIRDFResource* property, nsIRDFNode* target, PRBool tv, nsIRDFResource** source) { return mInner->GetSource(property, target, tv, source); } NS_IMETHOD GetSources(nsIRDFResource* property, nsIRDFNode* target, PRBool tv, nsISimpleEnumerator** sources) { return mInner->GetSources(property, target, tv, sources); } NS_IMETHOD GetTarget(nsIRDFResource* source, nsIRDFResource* property, PRBool tv, nsIRDFNode** target); NS_IMETHOD GetTargets(nsIRDFResource* source, nsIRDFResource* property, PRBool tv, nsISimpleEnumerator** targets) { return mInner->GetTargets(source, property, tv, targets); } NS_IMETHOD Assert(nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aTarget, PRBool aTruthValue); NS_IMETHOD Unassert(nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aTarget); NS_IMETHOD HasAssertion(nsIRDFResource* source, nsIRDFResource* property, nsIRDFNode* target, PRBool tv, PRBool* hasAssertion) { return mInner->HasAssertion(source, property, target, tv, hasAssertion); } NS_IMETHOD AddObserver(nsIRDFObserver* n) { return mInner->AddObserver(n); } NS_IMETHOD RemoveObserver(nsIRDFObserver* n) { return mInner->RemoveObserver(n); } NS_IMETHOD ArcLabelsIn(nsIRDFNode* node, nsISimpleEnumerator** labels) { return mInner->ArcLabelsIn(node, labels); } NS_IMETHOD ArcLabelsOut(nsIRDFResource* source, nsISimpleEnumerator** labels) { return mInner->ArcLabelsOut(source, labels); } NS_IMETHOD GetAllResources(nsISimpleEnumerator** aResult) { return mInner->GetAllResources(aResult); } NS_IMETHOD Flush(); NS_IMETHOD GetAllCommands(nsIRDFResource* source, nsIEnumerator/**/** commands); NS_IMETHOD IsCommandEnabled(nsISupportsArray/**/* aSources, nsIRDFResource* aCommand, nsISupportsArray/**/* aArguments, PRBool* aResult); NS_IMETHOD DoCommand(nsISupportsArray/**/* aSources, nsIRDFResource* aCommand, nsISupportsArray/**/* aArguments); }; //////////////////////////////////////////////////////////////////////// nsBookmarksService::nsBookmarksService() { NS_INIT_REFCNT(); } nsBookmarksService::~nsBookmarksService() { Flush(); NS_RELEASE(mInner); bm_ReleaseGlobals(); } nsresult nsBookmarksService::Init() { nsresult rv; rv = bm_AddRefGlobals(); if (NS_FAILED(rv)) return rv; rv = nsComponentManager::CreateInstance(kRDFInMemoryDataSourceCID, nsnull, nsIRDFDataSource::GetIID(), (void**) &mInner); if (NS_FAILED(rv)) return rv; rv = mInner->Init("rdf:bookmarks"); if (NS_FAILED(rv)) return rv; rv = ReadBookmarks(); if (NS_FAILED(rv)) return rv; // register this as a named data source with the RDF service rv = gRDF->RegisterDataSource(this, PR_FALSE); if (NS_FAILED(rv)) return rv; return NS_OK; } NS_IMETHODIMP NS_NewBookmarksService(nsISupports* aOuter, REFNSIID aIID, void** aResult) { NS_PRECONDITION(aResult != nsnull, "null ptr"); if (! aResult) return NS_ERROR_NULL_POINTER; NS_PRECONDITION(aOuter == nsnull, "no aggregation"); if (aOuter) return NS_ERROR_NO_AGGREGATION; nsresult rv = NS_OK; nsBookmarksService* result = new nsBookmarksService(); if (! result) return NS_ERROR_OUT_OF_MEMORY; rv = result->Init(); if (NS_SUCCEEDED(rv)) rv = result->QueryInterface(aIID, aResult); if (NS_FAILED(rv)) { delete result; *aResult = nsnull; return rv; } return rv; } //////////////////////////////////////////////////////////////////////// NS_IMPL_ADDREF(nsBookmarksService); NS_IMPL_RELEASE(nsBookmarksService); NS_IMETHODIMP nsBookmarksService::QueryInterface(REFNSIID aIID, void **aResult) { NS_PRECONDITION(aResult != nsnull, "null ptr"); if (! aResult) return NS_ERROR_NULL_POINTER; if (aIID.Equals(nsIBookmarksService::GetIID()) || aIID.Equals(kISupportsIID)) { *aResult = NS_STATIC_CAST(nsIBookmarksService*, this); } else if (aIID.Equals(nsIRDFDataSource::GetIID())) { *aResult = NS_STATIC_CAST(nsIRDFDataSource*, this); } else { *aResult = nsnull; return NS_NOINTERFACE; } NS_ADDREF(this); return NS_OK; } //////////////////////////////////////////////////////////////////////// // nsIBookmarksService NS_IMETHODIMP nsBookmarksService::AddBookmark(const char *aURI, const PRUnichar *aOptionalTitle) { // XXX for the moment, just add it as a child of BookmarksRoot BookmarkParser parser; parser.Init(nsnull, NS_STATIC_CAST(nsIRDFDataSource *, this)); nsresult rv = parser.AddBookmark(kNC_BookmarksRoot, aURI, aOptionalTitle, 0L, 0L, 0L, nsnull, kNC_Bookmark); if (NS_SUCCEEDED(rv)) { Flush(); } return(rv); } NS_IMETHODIMP nsBookmarksService::FindShortcut(const PRUnichar *aUserInput, char **aShortcutURL) { NS_PRECONDITION(aUserInput != nsnull, "null ptr"); if (! aUserInput) return NS_ERROR_NULL_POINTER; NS_PRECONDITION(aShortcutURL != nsnull, "null ptr"); if (! aShortcutURL) return NS_ERROR_NULL_POINTER; nsresult rv; nsCOMPtr literalTarget; rv = gRDF->GetLiteral(aUserInput, getter_AddRefs(literalTarget)); if (NS_FAILED(rv)) return rv; if (rv != NS_RDF_NO_VALUE) { nsCOMPtr source; rv = GetSource(kNC_ShortcutURL, literalTarget, PR_TRUE, getter_AddRefs(source)); if (NS_FAILED(rv)) return rv; if (rv != NS_RDF_NO_VALUE) { rv = source->GetValue(aShortcutURL); if (NS_FAILED(rv)) return rv; return NS_OK; } } *aShortcutURL = nsnull; return NS_RDF_NO_VALUE; } //////////////////////////////////////////////////////////////////////// // nsIRDFDataSource NS_IMETHODIMP nsBookmarksService::GetTarget(nsIRDFResource* aSource, nsIRDFResource* aProperty, PRBool aTruthValue, nsIRDFNode** aTarget) { nsresult rv; // If they want the URL... if (aTruthValue && aProperty == kNC_URL) { // ...and it is in fact a bookmark... PRBool hasAssertion; if (NS_SUCCEEDED(mInner->HasAssertion(aSource, kRDF_type, kNC_Bookmark, PR_TRUE, &hasAssertion)) && hasAssertion) { nsXPIDLCString uri; if (NS_FAILED(rv = aSource->GetValue( getter_Copies(uri) ))) { NS_ERROR("unable to get source's URI"); return rv; } nsAutoString ncURI(uri); if (ncURI.Find("NC:") == 0) { return(NS_RDF_NO_VALUE); } nsIRDFLiteral* literal; if (NS_FAILED(rv = gRDF->GetLiteral(nsAutoString(uri).GetUnicode(), &literal))) { NS_ERROR("unable to construct literal for URL"); return rv; } *aTarget = (nsIRDFNode*)literal; return NS_OK; } } return mInner->GetTarget(aSource, aProperty, aTruthValue, aTarget); } NS_IMETHODIMP nsBookmarksService::Assert(nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aTarget, PRBool aTruthValue) { if (CanAccept(aSource, aProperty, aTarget)) { return mInner->Assert(aSource, aProperty, aTarget, aTruthValue); } else { return NS_RDF_ASSERTION_REJECTED; } } NS_IMETHODIMP nsBookmarksService::Unassert(nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aTarget) { if (CanAccept(aSource, aProperty, aTarget)) { return mInner->Unassert(aSource, aProperty, aTarget); } else { return NS_RDF_ASSERTION_REJECTED; } } NS_IMETHODIMP nsBookmarksService::Flush() { return WriteBookmarks(mInner, kNC_BookmarksRoot); } NS_IMETHODIMP nsBookmarksService::GetAllCommands(nsIRDFResource* source, nsIEnumerator/**/** commands) { NS_NOTYETIMPLEMENTED("write me!"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsBookmarksService::IsCommandEnabled(nsISupportsArray/**/* aSources, nsIRDFResource* aCommand, nsISupportsArray/**/* aArguments, PRBool* aResult) { NS_NOTYETIMPLEMENTED("write me!"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsBookmarksService::DoCommand(nsISupportsArray* aSources, nsIRDFResource* aCommand, nsISupportsArray* aArguments) { NS_NOTYETIMPLEMENTED("write me!"); return NS_ERROR_NOT_IMPLEMENTED; } //////////////////////////////////////////////////////////////////////// // Implementation methods nsresult nsBookmarksService::ReadBookmarks() { nsresult rv; rv = gRDFC->MakeSeq(mInner, kNC_BookmarksRoot, nsnull); NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to make NC:BookmarksRoot a sequence"); if (NS_FAILED(rv)) return rv; nsSpecialSystemDirectory bookmarksFile(nsSpecialSystemDirectory::OS_CurrentProcessDirectory); // XXX we should get this from prefs. bookmarksFile += "res"; bookmarksFile += "rdf"; bookmarksFile += "bookmarks.html"; PRBool foundIERoot = PR_FALSE; #ifdef XP_WIN nsCOMPtr ieFolder; const char *ieFavoritesURL; #endif { // <-- scope the stream to get the open/close automatically. nsInputFileStream strm(bookmarksFile); if (! strm.is_open()) { NS_ERROR("unable to open file"); return NS_ERROR_FAILURE; } BookmarkParser parser; parser.Init(&strm, NS_STATIC_CAST(nsIRDFDataSource *, this)); #ifdef XP_MAC parser.SetIEFavoritesRoot(kURINC_IEFavoritesRoot); #endif #ifdef XP_WIN nsSpecialSystemDirectory ieFavoritesFile(nsSpecialSystemDirectory::Win_Favorites); nsFileURL ieFavoritesURLSpec(ieFavoritesFile); ieFavoritesURL = ieFavoritesURLSpec.GetAsString(); parser.SetIEFavoritesRoot(ieFavoritesURL); #endif parser.Parse(kNC_BookmarksRoot, kNC_Bookmark); parser.ParserFoundIEFavoritesRoot(&foundIERoot); } // <-- scope the stream to get the open/close automatically. // look for and import any IE Favorites nsAutoString ieTitle("Imported IE Favorites"); // XXX localization? #ifdef XP_MAC nsSpecialSystemDirectory ieFavoritesFile(nsSpecialSystemDirectory::Mac_PreferencesDirectory); ieFavoritesFile += "Explorer"; ieFavoritesFile += "Favorites.html"; nsInputFileStream ieStream(ieFavoritesFile); if (ieStream.is_open()) { if (NS_SUCCEEDED(rv = gRDFC->MakeSeq(mInner, kNC_IEFavoritesRoot, nsnull))) { BookmarkParser parser; parser.Init(&ieStream, this); parser.Parse(kNC_IEFavoritesRoot, kNC_IEFavorite); nsCOMPtr ieTitleLiteral; if (NS_SUCCEEDED(rv = gRDF->GetLiteral(ieTitle.GetUnicode(), getter_AddRefs(ieTitleLiteral)))) { rv = mInner->Assert(kNC_IEFavoritesRoot, kNC_Name, ieTitleLiteral, PR_TRUE); } // if the IE Favorites root isn't somewhere in bookmarks.html, add it if (!foundIERoot) { nsCOMPtr bookmarksRoot; rv = nsComponentManager::CreateInstance(kRDFContainerCID, nsnull, nsIRDFContainer::GetIID(), getter_AddRefs(container)); if (NS_SUCCEEDED(rv)) { rv = bookmarksRoot->AppendElement(kNC_IEFavoritesRoot); } } } } #endif #ifdef XP_WIN if (NS_SUCCEEDED(rv = gRDF->GetResource(ieFavoritesURL, getter_AddRefs(ieFolder)))) { nsCOMPtr ieTitleLiteral; if (NS_SUCCEEDED(rv = gRDF->GetLiteral(ieTitle.GetUnicode(), getter_AddRefs(ieTitleLiteral)))) { rv = mInner->Assert(ieFolder, kNC_Name, ieTitleLiteral, PR_TRUE); } // if the IE Favorites root isn't somewhere in bookmarks.html, add it if (!foundIERoot) { nsCOMPtr container; rv = nsComponentManager::CreateInstance(kRDFContainerCID, nsnull, nsIRDFContainer::GetIID(), getter_AddRefs(container)); if (NS_SUCCEEDED(rv)) { rv = container->AppendElement(ieFolder); } } } #endif return NS_OK; } nsresult nsBookmarksService::WriteBookmarks(nsIRDFDataSource *ds, nsIRDFResource *root) { nsSpecialSystemDirectory bookmarksFile(nsSpecialSystemDirectory::OS_CurrentProcessDirectory); // XXX we should get this from prefs. bookmarksFile += "res"; bookmarksFile += "rdf"; bookmarksFile += "bookmarks.html"; nsresult rv = NS_ERROR_FAILURE; nsOutputFileStream strm(bookmarksFile); if (strm.is_open()) { strm << "\n"; strm << "\n"; strm << "Bookmarks\n"; strm << "

Bookmarks

\n\n"; rv = WriteBookmarksContainer(ds, strm, root, 0); } return(rv); } nsresult nsBookmarksService::WriteBookmarksContainer(nsIRDFDataSource *ds, nsOutputFileStream strm, nsIRDFResource *parent, PRInt32 level) { nsresult rv = NS_OK; nsAutoString indentationString(""); for (PRInt32 loop=0; loop container; rv = nsComponentManager::CreateInstance(kRDFContainerCID, nsnull, nsIRDFContainer::GetIID(), getter_AddRefs(container)); if (NS_SUCCEEDED(rv)) { strm << indentation; strm << "

\n"; nsCOMPtr children; if (NS_SUCCEEDED(rv = container->GetElements(getter_AddRefs(children)))) { PRBool more = PR_TRUE; while (more == PR_TRUE) { if (NS_FAILED(rv = children->HasMoreElements(&more))) break; if (more != PR_TRUE) break; nsCOMPtr iSupports; if (NS_FAILED(rv = children->GetNext(getter_AddRefs(iSupports)))) break; nsCOMPtr child = do_QueryInterface(iSupports); if (!child) break; PRBool isIERoot = PR_FALSE, isContainer = PR_FALSE; if (NS_SUCCEEDED(child->EqualsResource(kNC_IEFavoritesRoot, &isIERoot))) { if (isIERoot == PR_FALSE) { if (NS_SUCCEEDED(rv = gRDFC->IsContainer(ds, child, &isContainer))) { } } } nsCOMPtr nameNode; nsAutoString nameString(""); char *name = nsnull; if (NS_SUCCEEDED(rv = ds->GetTarget(child, kNC_Name, PR_TRUE, getter_AddRefs(nameNode)))) { nsCOMPtr nameLiteral = do_QueryInterface(nameNode); PRUnichar *title = nsnull; if (NS_SUCCEEDED(rv = nameLiteral->GetValue(&title))) { nameString = title; name = nameString.ToNewCString(); } } strm << indentation; strm << " "; if (isContainer == PR_TRUE) { strm << "

GetValue(getter_Copies(id)); if (NS_SUCCEEDED(rv) && (id)) { strm << (const char*) id; } strm << "\""; strm << ">"; // output title if (name) strm << name; strm << "

\n"; rv = WriteBookmarksContainer(ds, strm, child, level+1); } else { char *url = nsnull; if (NS_SUCCEEDED(rv = child->GetValue(&url))) { if (url) { nsAutoString uri(url); // XXX What's the best way to determine if its a separator? if (uri.Find(kURINC_BookmarksRoot) == 0) { // its a separator strm << "
\n"; } else { strm << "
"; // output title if (name) strm << name; strm << "\n"; } } } } if (nsnull != name) { delete []name; name = nsnull; } if (NS_FAILED(rv)) break; } strm << indentation; strm << "

\n"; } } delete [] indentation; return(rv); } nsresult nsBookmarksService::WriteBookmarkProperties(nsIRDFDataSource *ds, nsOutputFileStream strm, nsIRDFResource *child, nsIRDFResource *property, const char *htmlAttrib, PRBool isFirst) { nsresult rv; nsCOMPtr node; if (NS_SUCCEEDED(rv = ds->GetTarget(child, property, PR_TRUE, getter_AddRefs(node)))) { nsCOMPtr literal = do_QueryInterface(node); if (literal) { PRUnichar *literalUni = nsnull; if (NS_SUCCEEDED(rv = literal->GetValue(&literalUni))) { nsAutoString literalString = literalUni; char *attribute = literalString.ToNewCString(); if (nsnull != attribute) { if (isFirst == PR_FALSE) { strm << " "; } strm << htmlAttrib; strm << attribute; strm << "\""; delete [] attribute; attribute = nsnull; } } } } return(rv); } PRBool nsBookmarksService::CanAccept(nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aTarget) { // XXX This is really crippled, and needs to be stricter. We want // to exclude any property that isn't talking about a known // bookmark. nsresult rv; PRBool isOrdinal; rv = gRDFC->IsOrdinalProperty(aProperty, &isOrdinal); if (NS_FAILED(rv)) return PR_FALSE; if (isOrdinal) { return PR_TRUE; } else if ((aProperty != kRDF_type) || (aProperty != kWEB_LastModifiedDate) || (aProperty != kWEB_LastVisitDate) || (aProperty != kNC_Name) || (aProperty != kNC_BookmarkAddDate)) { return PR_TRUE; } else { return PR_FALSE; } } //////////////////////////////////////////////////////////////////////// // Component Exports extern "C" PR_IMPLEMENT(nsresult) NSGetFactory(nsISupports* aServiceMgr, const nsCID &aClass, const char *aClassName, const char *aProgID, nsIFactory **aFactory) { NS_PRECONDITION(aFactory != nsnull, "null ptr"); if (! aFactory) return NS_ERROR_NULL_POINTER; nsIGenericFactory::ConstructorProcPtr constructor; if (aClass.Equals(kBookmarksServiceCID)) { constructor = NS_NewBookmarksService; } else { *aFactory = nsnull; return NS_NOINTERFACE; // XXX } nsresult rv; NS_WITH_SERVICE1(nsIComponentManager, compMgr, aServiceMgr, kComponentManagerCID, &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr factory; rv = compMgr->CreateInstance(kGenericFactoryCID, nsnull, nsIGenericFactory::GetIID(), getter_AddRefs(factory)); if (NS_FAILED(rv)) return rv; rv = factory->SetConstructor(constructor); if (NS_FAILED(rv)) return rv; *aFactory = factory; NS_ADDREF(*aFactory); return NS_OK; } extern "C" PR_IMPLEMENT(nsresult) NSRegisterSelf(nsISupports* aServMgr , const char* aPath) { nsresult rv; nsCOMPtr servMgr(do_QueryInterface(aServMgr, &rv)); if (NS_FAILED(rv)) return rv; NS_WITH_SERVICE1(nsIComponentManager, compMgr, servMgr, kComponentManagerCID, &rv); if (NS_FAILED(rv)) return rv; rv = compMgr->RegisterComponent(kBookmarksServiceCID, "Bookmarks", NS_BOOKMARKS_SERVICE_PROGID, aPath, PR_TRUE, PR_TRUE); rv = compMgr->RegisterComponent(kBookmarksServiceCID, "Bookmarks", NS_BOOKMARKS_DATASOURCE_PROGID, aPath, PR_TRUE, PR_TRUE); return NS_OK; } extern "C" PR_IMPLEMENT(nsresult) NSUnregisterSelf(nsISupports* aServMgr, const char* aPath) { nsresult rv; nsCOMPtr servMgr(do_QueryInterface(aServMgr, &rv)); if (NS_FAILED(rv)) return rv; NS_WITH_SERVICE1(nsIComponentManager, compMgr, servMgr, kComponentManagerCID, &rv); if (NS_FAILED(rv)) return rv; rv = compMgr->UnregisterComponent(kBookmarksServiceCID, aPath); return NS_OK; }