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