michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "FileSystemModule.h" michael@0: michael@0: #include "sqlite3.h" michael@0: #include "nsString.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: #include "nsIFile.h" michael@0: michael@0: namespace { michael@0: michael@0: struct VirtualTableCursorBase michael@0: { michael@0: VirtualTableCursorBase() michael@0: { michael@0: memset(&mBase, 0, sizeof(mBase)); michael@0: } michael@0: michael@0: sqlite3_vtab_cursor mBase; michael@0: }; michael@0: michael@0: struct VirtualTableCursor : public VirtualTableCursorBase michael@0: { michael@0: public: michael@0: VirtualTableCursor() michael@0: : mRowId(-1) michael@0: { michael@0: mCurrentFileName.SetIsVoid(true); michael@0: } michael@0: michael@0: const nsString& DirectoryPath() const michael@0: { michael@0: return mDirectoryPath; michael@0: } michael@0: michael@0: const nsString& CurrentFileName() const michael@0: { michael@0: return mCurrentFileName; michael@0: } michael@0: michael@0: int64_t RowId() const michael@0: { michael@0: return mRowId; michael@0: } michael@0: michael@0: nsresult Init(const nsAString& aPath); michael@0: nsresult NextFile(); michael@0: michael@0: private: michael@0: nsCOMPtr mEntries; michael@0: michael@0: nsString mDirectoryPath; michael@0: nsString mCurrentFileName; michael@0: michael@0: int64_t mRowId; michael@0: }; michael@0: michael@0: nsresult michael@0: VirtualTableCursor::Init(const nsAString& aPath) michael@0: { michael@0: nsCOMPtr directory = michael@0: do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); michael@0: NS_ENSURE_TRUE(directory, NS_ERROR_FAILURE); michael@0: michael@0: nsresult rv = directory->InitWithPath(aPath); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = directory->GetPath(mDirectoryPath); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = directory->GetDirectoryEntries(getter_AddRefs(mEntries)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = NextFile(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: VirtualTableCursor::NextFile() michael@0: { michael@0: bool hasMore; michael@0: nsresult rv = mEntries->HasMoreElements(&hasMore); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!hasMore) { michael@0: mCurrentFileName.SetIsVoid(true); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr entry; michael@0: rv = mEntries->GetNext(getter_AddRefs(entry)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr file = do_QueryInterface(entry); michael@0: NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); michael@0: michael@0: rv = file->GetLeafName(mCurrentFileName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mRowId++; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: int Connect(sqlite3* aDB, void* aAux, int aArgc, const char* const* aArgv, michael@0: sqlite3_vtab** aVtab, char** aErr) michael@0: { michael@0: static const char virtualTableSchema[] = michael@0: "CREATE TABLE fs (" michael@0: "name TEXT, " michael@0: "path TEXT" michael@0: ")"; michael@0: michael@0: int rc = sqlite3_declare_vtab(aDB, virtualTableSchema); michael@0: if (rc != SQLITE_OK) { michael@0: return rc; michael@0: } michael@0: michael@0: sqlite3_vtab* vt = new sqlite3_vtab(); michael@0: memset(vt, 0, sizeof(*vt)); michael@0: michael@0: *aVtab = vt; michael@0: michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: int Disconnect(sqlite3_vtab* aVtab ) michael@0: { michael@0: delete aVtab; michael@0: michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: int BestIndex(sqlite3_vtab* aVtab, sqlite3_index_info* aInfo) michael@0: { michael@0: // Here we specify what index constraints we want to handle. That is, there michael@0: // might be some columns with particular constraints in which we can help michael@0: // SQLite narrow down the result set. michael@0: // michael@0: // For example, take the "path = x" where x is a directory. In this case, michael@0: // we can narrow our search to just this directory instead of the entire file michael@0: // system. This can be a significant optimization. So, we want to handle that michael@0: // constraint. To do so, we would look for two specific input conditions: michael@0: // michael@0: // 1. aInfo->aConstraint[i].iColumn == 1 michael@0: // 2. aInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_EQ michael@0: // michael@0: // The first states that the path column is being used in one of the input michael@0: // constraints and the second states that the constraint involves the equal michael@0: // operator. michael@0: // michael@0: // An even more specific search would be for name='xxx', in which case we michael@0: // can limit the search to a single file, if it exists. michael@0: // michael@0: // What we have to do here is look for all of our index searches and select michael@0: // the narrowest. We can only pick one, so obviously we want the one that michael@0: // is the most specific, which leads to the smallest result set. michael@0: michael@0: for(int i = 0; i < aInfo->nConstraint; i++) { michael@0: if (aInfo->aConstraint[i].iColumn == 1 && aInfo->aConstraint[i].usable) { michael@0: if (aInfo->aConstraint[i].op & SQLITE_INDEX_CONSTRAINT_EQ) { michael@0: aInfo->aConstraintUsage[i].argvIndex = 1; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: // TODO: handle single files (constrained also by the name column) michael@0: } michael@0: michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: int Open(sqlite3_vtab* aVtab, sqlite3_vtab_cursor** aCursor) michael@0: { michael@0: VirtualTableCursor* cursor = new VirtualTableCursor(); michael@0: michael@0: *aCursor = reinterpret_cast(cursor); michael@0: michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: int Close(sqlite3_vtab_cursor* aCursor) michael@0: { michael@0: VirtualTableCursor* cursor = reinterpret_cast(aCursor); michael@0: michael@0: delete cursor; michael@0: michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: int Filter(sqlite3_vtab_cursor* aCursor, int aIdxNum, const char* aIdxStr, michael@0: int aArgc, sqlite3_value** aArgv) michael@0: { michael@0: VirtualTableCursor* cursor = reinterpret_cast(aCursor); michael@0: michael@0: if(aArgc <= 0) { michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: nsDependentString path( michael@0: reinterpret_cast(::sqlite3_value_text16(aArgv[0]))); michael@0: michael@0: nsresult rv = cursor->Init(path); michael@0: NS_ENSURE_SUCCESS(rv, SQLITE_ERROR); michael@0: michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: int Next(sqlite3_vtab_cursor* aCursor) michael@0: { michael@0: VirtualTableCursor* cursor = reinterpret_cast(aCursor); michael@0: michael@0: nsresult rv = cursor->NextFile(); michael@0: NS_ENSURE_SUCCESS(rv, SQLITE_ERROR); michael@0: michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: int Eof(sqlite3_vtab_cursor* aCursor) michael@0: { michael@0: VirtualTableCursor* cursor = reinterpret_cast(aCursor); michael@0: return cursor->CurrentFileName().IsVoid() ? 1 : 0; michael@0: } michael@0: michael@0: int Column(sqlite3_vtab_cursor* aCursor, sqlite3_context* aContext, michael@0: int aColumnIndex) michael@0: { michael@0: VirtualTableCursor* cursor = reinterpret_cast(aCursor); michael@0: michael@0: switch (aColumnIndex) { michael@0: // name michael@0: case 0: { michael@0: const nsString& name = cursor->CurrentFileName(); michael@0: sqlite3_result_text16(aContext, name.get(), michael@0: name.Length() * sizeof(char16_t), michael@0: SQLITE_TRANSIENT); michael@0: break; michael@0: } michael@0: michael@0: // path michael@0: case 1: { michael@0: const nsString& path = cursor->DirectoryPath(); michael@0: sqlite3_result_text16(aContext, path.get(), michael@0: path.Length() * sizeof(char16_t), michael@0: SQLITE_TRANSIENT); michael@0: break; michael@0: } michael@0: default: michael@0: NS_NOTREACHED("Unsupported column!"); michael@0: } michael@0: michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: int RowId(sqlite3_vtab_cursor* aCursor, sqlite3_int64* aRowid) michael@0: { michael@0: VirtualTableCursor* cursor = reinterpret_cast(aCursor); michael@0: michael@0: *aRowid = cursor->RowId(); michael@0: michael@0: return SQLITE_OK; michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: namespace mozilla { michael@0: namespace storage { michael@0: michael@0: int RegisterFileSystemModule(sqlite3* aDB, const char* aName) michael@0: { michael@0: static sqlite3_module module = { michael@0: 1, michael@0: Connect, michael@0: Connect, michael@0: BestIndex, michael@0: Disconnect, michael@0: Disconnect, michael@0: Open, michael@0: Close, michael@0: Filter, michael@0: Next, michael@0: Eof, michael@0: Column, michael@0: RowId, michael@0: nullptr, michael@0: nullptr, michael@0: nullptr, michael@0: nullptr, michael@0: nullptr, michael@0: nullptr, michael@0: nullptr michael@0: }; michael@0: michael@0: return sqlite3_create_module(aDB, aName, &module, nullptr); michael@0: } michael@0: michael@0: } // namespace storage michael@0: } // namespace mozilla