/*
 * Worldvisions Weaver Software:
 *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
 *
 * Directory iterator.  Recursively uses opendir and readdir, so you don't
 * have to.  Basically implements 'find'.
 *
 */

#include "wvdiriter.h"

#if defined(_WIN32) && !defined(S_ISDIR)
#define S_ISDIR(x) (_S_IFDIR | (x))
#endif
#ifdef _WIN32
#define lstat stat
#endif

WvDirIter::WvDirIter( WvStringParm _dirname,
		      bool _recurse, bool _skip_mounts, size_t sizeof_stat )
    : relpath(""), dir(dirs)
/****************************************************************************/
{
    // if this assertion fails, then you probably used different compiler
    // options for the wvstreams library and the calling program.  Check
    // for defines like _FILE_OFFSET_BITS=64 and _LARGEFILE_SOURCE.
    assert(sizeof_stat == sizeof(struct stat));
    
    recurse = _recurse;
    go_up   = false;
    skip_mounts = _skip_mounts;
    found_top = false;

    WvString dirname(_dirname);
    int dl = strlen(dirname);
    if (dl != 0 && dirname[dl-1] == '/')
        dirname.edit()[dl-1] = 0;

    DIR * d = opendir( dirname );
    if( d ) {
        Dir * dd = new Dir( d, dirname );
        dirs.prepend( dd, true );
    }
}

WvDirIter::~WvDirIter()
/*********************/
{
    dirs.zap();
}

bool WvDirIter::isok() const
/**************************/
{
    return( !dirs.isempty() );
}

bool WvDirIter::isdir() const
/***************************/
{
    return( S_ISDIR( info.st_mode ) );
}

void WvDirIter::rewind()
/**********************/
{
    // have to closedir() everything that isn't the one we started with,
    // and rewind that.
    while( dirs.count() > 1 ) {
        dir.rewind();
        dir.next();
        dir.unlink();
    }

    if( isok() ) {
        dir.rewind();
        dir.next();
        rewinddir( dir->d );
    }
}


bool WvDirIter::next()
/********************/
// use readdir... and if that returns a directory, opendir() it and prepend
// it to dirs, so we start reading it until it's done.
{
    struct dirent * dent = NULL;

    if( !isok() )
        return( false );

    bool tryagain;
    do {
        bool ok = false;
        tryagain = false;

        // unrecurse if the user wants to
        if( go_up ) {
            go_up = false;
            if( dirs.count() > 1 ) {
                dir.unlink();
                dir.rewind();
                dir.next();
            } else
                return( false );
        }

        do {
            dent = readdir( dir->d );
            if( dent ) {
                info.fullname = WvString( "%s/%s", dir->dirname, dent->d_name );
                info.name = dent->d_name;

                if (relpath == "")
                    info.relname = info.name;
                else
                    info.relname = WvString("%s%s", relpath, info.name);

                ok = ( lstat( info.fullname, &info ) == 0
                            && strcmp( dent->d_name, "." )
                            && strcmp( dent->d_name, ".." ) );

                if (ok && !found_top)
                {
                    lstat(info.fullname, &topdir);
                    topdir.fullname = info.fullname;
                    topdir.name = info.name;
                    topdir.relname = info.relname;
                    found_top = true;
                }
            }
        } while( dent && !ok );

        if( dent ) {
            // recurse?
            if( recurse && S_ISDIR( info.st_mode ) &&
                    ( !skip_mounts || info.st_dev == topdir.st_dev) ) {
                DIR * d = opendir( info.fullname );
                if( d ) {
                    relpath = WvString( "%s%s/", relpath, info.name );
                    Dir * dd = new Dir( d, info.fullname );
                    dirs.prepend( dd, true );
                    dir.rewind();
                    dir.next();
                }
            }
        } else {
            // end of directory.  if we recursed, unlink it and go up a 
            // notch.  if this is the top level, DON'T close it, so that
            // the user can ::rewind() again if he wants.
            if( dirs.count() > 1 ) {
                if (dirs.count() == 2)
                    relpath = WvString("");
                else
                    relpath = WvString( "%s/", getdirname(relpath) );

                dir.unlink();
                dir.rewind();
                dir.next();
                tryagain = true;
            }
        }

    } while( tryagain );

    return( dent != NULL );
}

