root/src/apps/text_search/InitialIterator.cpp
/*
 * Copyright (c) 2008 Stephan Aßmus <superstippi@gmx.de>
 * Copyright (c) 1998-2007 Matthijs Hollemans
 * All rights reserved. Distributed under the terms of the MIT License.
 */

#include "InitialIterator.h"

#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <Directory.h>

#include "Model.h"

using std::nothrow;

// TODO: stippi: Check if this is a the best place to maintain a global
// list of files and folders for node monitoring. It should probably monitor
// every file that was grepped, as well as every visited (sub) folder.
// For the moment I don't know the life cycle of the InitialIterator object.


InitialIterator::InitialIterator(const Model* model)
        : FileIterator(),
          fDirectories(32),
          fCurrentDir(new (nothrow) BDirectory(&model->fDirectory)),
          fCurrentRef(0),

          fSelectedFiles(model->fSelectedFiles),

          fRecurseDirs(model->fRecurseDirs),
          fRecurseLinks(model->fRecurseLinks),
          fSkipDotDirs(model->fSkipDotDirs),
          fTextOnly(model->fTextOnly)
{
        if (!fCurrentDir || !fDirectories.AddItem(fCurrentDir)) {
                // init error
                delete fCurrentDir;
                fCurrentDir = NULL;
        }
}


InitialIterator::~InitialIterator()
{
        for (int32 i = fDirectories.CountItems() - 1; i >= 0; i--)
                delete (BDirectory*)fDirectories.ItemAt(i);
}


bool
InitialIterator::IsValid() const
{
        return fCurrentDir != NULL;
}


bool
InitialIterator::GetNextName(char* buffer)
{
        BEntry entry;
        struct stat fileStat;

        while (true) {
                // Traverse the directory to get a new BEntry.
                // _GetNextEntry returns false if there are no
                // more entries, and we exit the loop.

                if (!_GetNextEntry(entry))
                        return false;

                // If the entry is a subdir, then add it to the
                // list of directories and continue the loop.
                // If the entry is a file and we can grep it
                // (i.e. it is a text file), then we're done
                // here. Otherwise, continue with the next entry.

                if (entry.GetStat(&fileStat) == B_OK) {
                        if (S_ISDIR(fileStat.st_mode)) {
                                // subdir
                                _ExamineSubdir(entry);
                        } else {
                                // file or a (non-traversed) symbolic link
                                if (_ExamineFile(entry, buffer, fTextOnly))
                                        return true;
                        }
                }
        }
}


bool
InitialIterator::NotifyNegatives() const
{
        return false;
}


bool
InitialIterator::GetTopEntry(BEntry& entry)
{
        // If the user selected one or more files, we must look
        // at the "refs" inside the message that was passed into
        // our add-on's process_refs(). If the user didn't select
        // any files, we will simply read all the entries from the
        // current working directory.

        entry_ref fileRef;

        if (fSelectedFiles.FindRef("refs", fCurrentRef, &fileRef) == B_OK) {
                entry.SetTo(&fileRef, fRecurseLinks);
                ++fCurrentRef;
                return true;
        } else if (fCurrentRef > 0) {
                // when we get here, we have processed
                // all the refs from the message
                return false;
        } else if (fCurrentDir != NULL) {
                // examine the whole directory
                return fCurrentDir->GetNextEntry(&entry, fRecurseLinks) == B_OK;
        }

        return false;
}


bool
InitialIterator::FollowSubdir(BEntry& entry) const
{
        if (!fRecurseDirs)
                return false;

        if (fSkipDotDirs) {
                char nameBuf[B_FILE_NAME_LENGTH];
                if (entry.GetName(nameBuf) == B_OK) {
                        if (*nameBuf == '.')
                                return false;
                }
        }

        return true;
}


// #pragma mark - private


bool
InitialIterator::_GetNextEntry(BEntry& entry)
{
        if (fDirectories.CountItems() == 1)
                return GetTopEntry(entry);
        else
                return _GetSubEntry(entry);
}


bool
InitialIterator::_GetSubEntry(BEntry& entry)
{
        if (!fCurrentDir)
                return false;

        if (fCurrentDir->GetNextEntry(&entry, fRecurseLinks) == B_OK)
                return true;

        // If we get here, there are no more entries in
        // this subdir, so return to the parent directory.

        fDirectories.RemoveItem(fCurrentDir);
        delete fCurrentDir;
        fCurrentDir = (BDirectory*)fDirectories.LastItem();

        return _GetNextEntry(entry);
}


void
InitialIterator::_ExamineSubdir(BEntry& entry)
{
        if (!FollowSubdir(entry))
                return;

        BDirectory* dir = new (nothrow) BDirectory(&entry);
        if (dir == NULL || dir->InitCheck() != B_OK || !fDirectories.AddItem(dir)) {
                // clean up
                delete dir;
                return;
        }

        fCurrentDir = dir;
}