/* Copyright 1996 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
     List files as specified in a selection.

Revision History:

0.00  02-Jun-89  JSR  Created from extracts of c.actionwind.

0.01  27-Jun-89  JSR  Update to cope with arbitrary length file names.

0.02  29-Sep-89  JSR  Use overflowing_ memory allocation routines.
                      Add selection_summary.
                      Add name_of_next_node.

0.03  17-Oct-89  JSR  Upgrade next_nodename and next_filename to not
                      prepend the directory if the directory is a nul
                      string.
*/

#if 0
#define debuglist
#endif

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

#ifndef __os_h
#include "os.h"
#endif

#include "Options.h"
#include "malloc+.h"
#include "listfiles.h"
#include "allerrs.h"
#include "debug.h"
#include "memmanage.h"


#define No 0
#define Yes (!No)

#define Directory_Buffer_Size 20
#define Temp_DirBuffer_Size 640
#define Directory_File_Type 2

typedef struct
{
     int  load_address;
     int  execution_address;
     int  length;
     int  attributes;
     int  object_type;
     char *object_name;

     #ifdef USE_PROGRESS_BAR
     void *chain_ref;
     #endif

}    directory_buffer_entry;

typedef struct Search_Nest_Level
{
     struct Search_Nest_Level *next_search_nest_level;
     int  offset_to_next_item;
     int  entries_in_buffer;
     int  next_entry_to_return;
     directory_buffer_entry   directory_buffer[ Directory_Buffer_Size ];

     #ifdef USE_PROGRESS_BAR
     int total_entries;
     int total_progress;
     int progress_per_object;
     #endif

}    search_nest_level;

typedef struct File_Selection
{
     struct File_Selection *next_file;
     char *selection_name;
}    file_selection;

/*
     This enumerates the states for finding the next leaf to process.
*/
typedef enum
{
     Next_Leaf,               /* Find the next leaf at this level */
     Process_Node,            /* Process that found by Next_Leaf */
     Read_Next_Cache_Full,    /* Read another directory cache-full */
     Nest_Into_Directory      /* Start up into a nested directory */
}    next_leaf_state;

typedef struct search_context
{
    search_nest_level       *nested_filenames;
    char                    *directory;
    file_selection          *selection;
    file_selection          **last_selections_link;
    next_leaf_state         action;
    unsigned int            selections_size;
    unsigned int            selections_loadaddr;
    unsigned int            selections_execaddr;
    unsigned int            selections_attributes;
    unsigned int            selections_objecttype;
    unsigned int            recursive:1;
    unsigned int            directories_first:1;
    unsigned int            directories_last:1;
    unsigned int            partitions_as_directories:1;

    #ifdef USE_PROGRESS_BAR
    int total_entries;
    int total_progress;
    int progress_per_object;
    void *chain_ref;
    #endif

} search_context;


#ifdef USE_PROGRESS_BAR
#include "swis.h"
#define SIZE 2048
static int count_objects_in_dir( char *dir )
{
    os_error *e;
    char      buff[SIZE];
    int       chunk = 256, context = 0, c, num = 0, total = 0;

    while (context != -1)
    {
        if ((e = _swix(OS_GBPB, _INR(0,6) | _OUTR(3,4), 9, dir, buff, chunk, context, SIZE, 0, &num, &c)) == NULL)
        {
            if (c == context)
            {
              /*
                 Broken archives cause context to never be updated ... not sure why
                 the SWI does not return an error.
              */
              DEBUG(("count_objects_in_dir: aborting\n"));
              return 0;
            }

            context = c;
            total += num;
        }
        else
        {
            DEBUG(("count_objects_in_dir: %s\n", e->errmess));
            return 0;
        }
    }

    return total;
}
#undef SIZE
#endif


/*
     This initialises an empty search context.
     The search will return a list of files or directories, which must
     be added to the context after it has been created. The context is
     created recursive.
*/
os_error *create_search_context( search_handle *handle )
{
    search_context *new_context;

    if ( ( new_context = overflowing_malloc( sizeof( search_context ))) != NULL )
    {
        *handle = (search_handle)new_context;
        new_context->directory = NULL;
        new_context->nested_filenames = NULL;
        new_context->selection = NULL;
        new_context->last_selections_link = &new_context->selection;
        new_context->recursive = Yes;
        new_context->directories_first = No;
        new_context->directories_last = No;
        new_context->partitions_as_directories = Yes;
        new_context->action = Process_Node;

        #ifdef USE_PROGRESS_BAR
        new_context->total_entries = 0;
        new_context->total_progress = PROGRESS_MAX;
        new_context->progress_per_object = 0;
        new_context->chain_ref = NULL;
        #endif

        return NULL;
    }
    else
    {
        return error( mb_malloc_failed );
    }
}


static int is_a_directory( search_handle handle, int objecttype )
{
    if ( objecttype == ObjectType_Directory ||
            (objecttype == (ObjectType_Directory | ObjectType_File) &&
             handle->partitions_as_directories) )
    {
            return Yes;
    }
    else
    {
            return No;
    }
}

/*
     Sets whether the search is recursive or not
*/
void recurse_search_context( search_handle handle, BOOL recursive )
{
    /*
            Make sure when changing to recursive that the children of this
            directory get returned
    */
    if ( !handle->recursive &&
         recursive &&
         is_a_directory( handle, objecttype_of_next_node( handle )) &&
         handle->action == Next_Leaf )
    {
            handle->action = Nest_Into_Directory;
    }

    handle->recursive = recursive != No;
}

/*
    Sets whether directories should be returned before their contents.
    This is relevant only when recursing.
*/
void return_directories_first( search_handle handle, BOOL directories_first )
{
    ((search_context *)handle)->directories_first = directories_first != No;
}

/*
    Sets whether directories should be returned after their contents.
    This is relevant only when recursing.
*/
void return_directories_last( search_handle handle, BOOL directories_last )
{
    handle->directories_last = directories_last != No;
}

/*
     Sets whether partitions should be treated the same as directories or not.
     This is relevant only when recursing.
*/
void treat_partitions_as_directories( search_handle handle, BOOL partitions_are_directories )
{
    handle->partitions_as_directories = partitions_are_directories;
}

/*
     Changes the directory in which the search will take place
*/
os_error *set_directory( search_handle handle, char *directory )
{
     search_context *context = (search_context *)handle;

     if ( context->directory )
     {
      overflowing_free( context->directory );
     }

     if ( ( context->directory = overflowing_malloc( strlen( directory ) + 1 )) != NULL )
     {
      strcpy( context->directory, directory );

      return NULL;
     }
     else
     {
      return error( mb_malloc_failed );
     }
}

/*
     frees the passed selection
*/
static void free_selection( file_selection *selection )
{
     overflowing_free( selection->selection_name );
     overflowing_free( selection );
}

/*
     Clears down a whole selection
*/
void clear_selection( search_handle handle )
{
     search_context *context = (search_context *)handle;
     file_selection *next_selection;
     file_selection *this_selection;

     /*
      Free the chain of selections
     */
     for ( this_selection = context->selection;
       this_selection != NULL;
       this_selection = next_selection )
     {
      next_selection = this_selection->next_file;
      free_selection( this_selection );
     }

     /*
      close off the head of the list
     */
     context->selection = NULL;
     context->last_selections_link = &context->selection;
}

/*
     Add a selection to an initialised search context. The search context
     will return found files in the order in which they were added.
*/
os_error *add_selection( search_handle handle, char *file_name, int wordlen )
{
     file_selection *new_selection;
     search_context *context = (search_context *)handle;

     /*
      These two if statements allocate the new selection structure and
      then some space for the file name. The mallocs are checked to
      work, and if they don't everything is tidied up and an error
      is returned.
     */
     new_selection = overflowing_malloc( sizeof( file_selection ));
     if ( new_selection != NULL )
     {
      new_selection->selection_name = overflowing_malloc( wordlen + 1 );
      if ( new_selection->selection_name != NULL )
      {
           /*
        Everything allocated OK, so copy the string and
        link the new selection onto the end of the list.
           */
           *context->last_selections_link = new_selection;
           strncpy( new_selection->selection_name, file_name, wordlen );
           new_selection->selection_name[ wordlen ] = '\0';
           context->last_selections_link = &new_selection->next_file;
           new_selection->next_file = NULL;

           DEBUG(("context: %x new selection: %s\n", context, new_selection->selection_name));
           #ifdef USE_PROGRESS_BAR
           context->total_entries++;
           context->progress_per_object = context->total_progress / context->total_entries;
           #endif

           return NULL;
      }
      else
      {
           /*
        No room for selection name.
        Free up the selection structure and tidy up the ends
           */
           overflowing_free( new_selection );

           return error( mb_malloc_failed );
      }
     }
     else
     {
      return error( mb_malloc_failed );
     }
}

/*
     Free the abstract data type search_context. This is an equivalent
     to free(), but for this data type. It free all things hanging off it
     as well as the base structure.
*/
void dispose_search_context( search_handle handle )
{
     search_context *context = (search_context *)handle;
     search_nest_level *rover;
     search_nest_level *temp_rover;
     int i;

     /*
      Free the search nest level chain
     */
     rover = context->nested_filenames;

     while( rover != NULL )
     {
      temp_rover = rover;
      rover = rover->next_search_nest_level;
      for ( i = 0; i < Directory_Buffer_Size; i++ )
      {
           overflowing_free( temp_rover->directory_buffer[ i ].object_name );
      }
      overflowing_free( temp_rover );
     }

     clear_selection( handle );

     overflowing_free( context->directory );

     overflowing_free( context );
}

static int size_of_next_filename( search_context *context, search_nest_level *nesting )
{
     search_nest_level *nest_level = nesting;
     int returned_length = 0;

     if ( context->directory &&
      context->selection &&
      context->selection->selection_name )
     {
      returned_length = strlen( context->directory );

      /*
           Only Add one in if there's going to be a . in between dir and selection
      */
      if ( returned_length )
           returned_length++;

      returned_length += strlen( context->selection->selection_name );

      if ( context->recursive )
      {
           while ( nest_level != NULL )
           {
               returned_length += 1 + strlen( nest_level->
                                             directory_buffer[ nest_level->next_entry_to_return ].
                                             object_name );
               nest_level = nest_level->next_search_nest_level;
           }
      }
     }

     return returned_length;
}

/*
     Returns a pointer to the next filename in search_context

     This is a 'malloc'ed piece of memory, which should be 'free'ed when
     it is no longer needed.
*/
static os_error *next_filename( search_context *context, search_nest_level *nesting, char **hook_location )
{
     search_nest_level *nest_level = nesting;
     char *buffer = NULL;
     char *rover;
     int next_filename_size = size_of_next_filename( context, nesting );
     int part_length;

     if ( next_filename_size > 0 )
     {
      buffer = overflowing_malloc( next_filename_size + 1 );

      if ( buffer == NULL )
      {
           return error( mb_malloc_failed );
      }
     }

     if ( buffer != NULL )
     {
      if ( context->directory[0] )
      {
           sprintf( buffer, "%s.%s", context->directory, context->selection->selection_name );
      }
      else
      {
           sprintf( buffer, "%s", context->selection->selection_name );
      }

      rover = buffer + next_filename_size;

      *rover = '\0';

      while ( nest_level != NULL )
      {
           part_length = strlen( nest_level->
             directory_buffer[ nest_level->next_entry_to_return ].
             object_name );
           rover -= part_length + 1;

           rover[0] = '.';

           memcpy( &rover[1], nest_level->
             directory_buffer[ nest_level->next_entry_to_return ].
             object_name, part_length );

           nest_level = nest_level->next_search_nest_level;
      }
     }

     *hook_location = buffer;

     return NULL;
}

os_error *next_nodename( search_handle handle, char **hook_location )
{
     search_context *context = (search_context *)handle;
     return next_filename( context, context->nested_filenames, hook_location );
}

os_error *selection_summary( search_handle handle, char **summary )
{
    search_context *context = (search_context *)handle;
    char *cont;

    if ( context->selection == NULL )
    {
            cont = msgs_lookup("93"); /* 'nothing' */
    }
    else if ( context->selection->next_file == NULL )
    {
            cont = context->selection->selection_name;
    }
    else
    {
            cont = msgs_lookup("92"); /* 'many' */
    }

    *summary = overflowing_malloc( strlen( context->directory ) + 1 + strlen( cont ) + 1 );

    if ( *summary == NULL )
            return error( mb_malloc_failed );

    sprintf( *summary, "%s.%s", context->directory, cont );

    return NULL;
}

/*
     Finds the degree by which mstring matches the current next node.
     It returns the next mismatching position, and other information
     concerning where the mismatch occured
*/
static char *first_position_not_matched( search_handle handle, search_nest_level *nl, char *mstring,
     search_nest_level **deepest_match, search_nest_level **shallowest_non_match )
{
     char *matched_position;
     char *ms;
     int dl;
     int sl;

     if ( nl == NULL )
     {
      matched_position = mstring;

      dl = strlen( handle->directory );

      if ( strncmp( handle->directory, matched_position, dl ) != 0 )
           return NULL;

      matched_position += dl;

      if ( *matched_position != '.' )
           return NULL;

      matched_position += 1;

      sl = strlen( handle->selection->selection_name );

      if ( strncmp( handle->selection->selection_name, matched_position, sl ) != 0 )
           return NULL;

      matched_position += sl;

      return matched_position;
     }
     else
     {
      matched_position = first_position_not_matched( handle, nl->next_search_nest_level, mstring,
           deepest_match, shallowest_non_match );

      if ( matched_position == NULL )
           return NULL;

      ms = nl->directory_buffer[ nl->next_entry_to_return ].object_name;
      sl = strlen( ms );

      if ( *matched_position == '.' &&
           strncmp( matched_position + 1, ms, sl ) == 0 )
      {
           matched_position += 1 + sl;

           *deepest_match = nl;
      }
      else
      {
           if ( nl->next_search_nest_level == *deepest_match )
           {
        *shallowest_non_match = nl;
           }
      }

      return matched_position;
     }
}

/*
    Informs that next node has been deleted
*/
void deleted_next_node( search_handle handle, char *deleted_node )
{
    if ( handle->nested_filenames && handle->selection )
    {
            char *matched_position;
            search_nest_level *deepest_match = NULL;
            search_nest_level *shallowest_non_match = NULL;

            matched_position = first_position_not_matched( handle, handle->nested_filenames, deleted_node, &deepest_match, &shallowest_non_match );

            if ( matched_position != NULL )
            {
            /*
            Deleted node is before something in shallowest non-match
            */
            if ( matched_position == strrchr( deleted_node, '.' ) &&
                 shallowest_non_match != NULL )
            {
                    shallowest_non_match->offset_to_next_item--;
            }

            /*
                    Deleted is current node or a parent of it (gulp!)
            */
            if ( *matched_position == '\0' &&
                 deepest_match != NULL )
            {
                    deepest_match->offset_to_next_item--;
            }
            }
    }
}

/*
     Read the parameters for the next node, don't update
     if an error occurs
*/
void read_next_node_parameters( search_handle handle )
{
    os_filestr fileplace;
    char *filename;
    os_error *err;

    err = next_nodename( handle, &filename );

    if ( err )
            return;

    fileplace.action = 5; /* read catalogue information */
    fileplace.name = filename;

    err = os_file( &fileplace );

    if ( err )
            return;

    handle->selections_size       = fileplace.start;
    handle->selections_loadaddr   = fileplace.loadaddr;
    handle->selections_execaddr   = fileplace.execaddr;
    handle->selections_attributes = fileplace.end;
}

typedef enum {
  the_size, the_load_address, the_execute_address, the_attributes, the_type, the_name
  #ifdef USE_PROGRESS_BAR
  , the_progress, the_ref_ptr
  #endif
} which_thing;

/*
    Returns one of the values associated with the next node.
    If the node doesn't exist the value not_found is returned.
*/
static unsigned int thing_of_next_node( search_handle handle, which_thing thing, int not_found )
{
    search_context *context = (search_context *)handle;
    search_nest_level *nf;

    if ( context->selection )
    {
        if ( (nf = context->nested_filenames) == NULL )
            {
            switch( thing )
            {
            case the_size:
                    return context->selections_size;
            case the_load_address:
                    return context->selections_loadaddr;
            case the_execute_address:
                    return context->selections_execaddr;
            case the_attributes:
                    return context->selections_attributes;
            case the_type:
                    return context->selections_objecttype;
            case the_name:
                    return (int)context->selection->selection_name;

            #ifdef USE_PROGRESS_BAR
            case the_progress:
              if (context->selections_objecttype == ObjectType_Directory)
              {
                DEBUG(("ponn: selection: %08x\n", 0));
                return 0;
              } else
              {
                DEBUG(("ponn: selection: %08x\n", context->progress_per_object));
                return context->progress_per_object;
              }

            case the_ref_ptr:
                return (unsigned int) &context->chain_ref;
            #endif

            } /* end switch */
        }
        else
        {
            directory_buffer_entry *d;

            d = nf->directory_buffer + nf->next_entry_to_return;

            switch( thing )
            {
            case the_size:
                    return d->length;
            case the_load_address:
                    return d->load_address;
            case the_execute_address:
                    return d->execution_address;
            case the_attributes:
                    return d->attributes;
            case the_type:
                    return d->object_type;
            case the_name:
                    return (int) d->object_name;

            #ifdef USE_PROGRESS_BAR
            case the_progress:
              if (d->object_type == ObjectType_Directory)
              {
                DEBUG(("ponn: nested: %08x\n", 0));
                return 0;
              }
              else
              {
                DEBUG(("ponn: nested: %08x\n", nf->progress_per_object));
                return nf->progress_per_object;
              }

            case the_ref_ptr:
                return (int) &d->chain_ref;
            #endif

            }
        }
    }

    DEBUG(("tonn: not found\n"));

    return not_found;
}

unsigned int size_of_next_node( search_handle handle )
{
    return thing_of_next_node( handle, the_size, -1 );
}

unsigned int reload_of_next_node( search_handle handle )
{
    return thing_of_next_node( handle, the_load_address, -1 );
}

unsigned int execute_of_next_node( search_handle handle )
{
    return thing_of_next_node( handle, the_execute_address, -1 );
}

unsigned int attributes_of_next_node( search_handle handle )
{
    return thing_of_next_node( handle, the_attributes, -1 );
}

unsigned int objecttype_of_next_node( search_handle handle )
{
    return thing_of_next_node( handle, the_type, ObjectType_NotFound );
}

char *name_of_next_node( search_handle handle )
{
    return (char *)thing_of_next_node( handle, the_name, NULL );
}


#ifdef USE_PROGRESS_BAR
unsigned int progress_of_next_node( search_handle handle )
{
    return thing_of_next_node( handle, the_progress, 0 );
}


unsigned int chain_ref_ptr_of_next_node( search_handle handle )
{
    return thing_of_next_node( handle, the_ref_ptr, 0 );
}
#endif


/*
    Assuming a directory has just been found, return whether this return
    of this directory was before or after its contents.
*/
unsigned int directory_is_after_contents( search_handle handle )
{
    search_context *context = (search_context *)handle;

    return context->action == Next_Leaf;
}

/*
     Returns 0 if no more nodes
     Returns non-0 if more nodes
*/
unsigned int another_node( search_handle handle )
{
    return ((search_context *)handle)->selection != NULL;
}

/*
    When finding a selection fails call this to skip it.
*/
void skip_failed_selection( search_handle handle )
{
    search_context *context = (search_context *)handle;

    switch ( context->action )
    {
    case Next_Leaf:
            /*
            Can't generate an error in this state
            */
            break;

    case Process_Node:
            context->action = Next_Leaf;
            break;

    case Read_Next_Cache_Full:
            /*
            This forces un-nesting then continuing at the upper level
            */
            context->nested_filenames->offset_to_next_item = -1;
            break;

    case Nest_Into_Directory:
            context->action = Next_Leaf;
            break;
    }
}

void skip_list_file( search_handle handle )
{
    search_context *context = (search_context *)handle;

    context->action = Next_Leaf;
}

os_error *step_to_next_node( search_handle handle, unsigned int *progress )
{
    search_context *context = (search_context *)handle;
    search_nest_level *temp_context;
    os_gbpbstr gbpbplace;
    os_filestr fileplace;
    char temp_directory_buffer[ Temp_DirBuffer_Size ];
    int i;
    int pos;
    BOOL resolved = No;
    os_error *err = NULL;
    file_selection *next_selection;
    int objecttype;

    DEBUG(("\nstep_to_next_node\n"));

    /*
        While the answer hasn't resolved itself and there's no error
        then try the next step of resolving the answer
    */
    while( !resolved && !err )
    {
        switch( context->action )
        {
        case Next_Leaf: DEBUG(("Next_Leaf "));
        /*
            Step to the next leaf at this nesting level
        */
        if ( context->nested_filenames == NULL )
        {
            /*
                Get next selection
            */
            next_selection = context->selection->next_file;
            free_selection( context->selection );
            context->selection = next_selection;

            DEBUG(("get next selection"));

            context->action = Process_Node;
        }
        else
        {
            /*
                Get next cached entry
            */
            context->nested_filenames->next_entry_to_return++;
            if ( context->nested_filenames->next_entry_to_return >=
             context->nested_filenames->entries_in_buffer )
            {
                /*
                    We've run out of cached entries
                */
                DEBUG(("cache empty "));
                context->action = Read_Next_Cache_Full;
            }
            else
            {
                /*
                    Found an entry in the cache, so let's
                    process it
                */
                DEBUG(("cache ok "));
                context->action = Process_Node;
            }
        }
        break;

        case Process_Node: DEBUG(("Process_Node "));
        /*
            Process the node as supplied by Next_Leaf
        */
        if ( context->selection == NULL )
        {
            /*
                No more entries in the selection, so we've
                resolved what happens next
            */
            DEBUG(("resolved all "));
            resolved = Yes;
        }
        else
        {
            DEBUG(("%s ", name_of_next_node(context)));
            /*
                Get type of node if needed
            */
            if ( context->nested_filenames == NULL )
            {
                fileplace.action = 17;
                err = next_filename( context, context->nested_filenames, &fileplace.name );
                DEBUG(("%s ", fileplace.name));

                if ( err )
                {
                    DEBUG(("err\n"));
                    continue;
                }

                err = os_file( &fileplace );

                if ( err )
                {
                    overflowing_free( fileplace.name );
                    DEBUG((" error\n"));
                    continue;
                }

                /*
                    Didn't find a selection - generate an error.
                */
                if ( fileplace.action == ObjectType_NotFound )
                {
                    fileplace.loadaddr = fileplace.action;
                    fileplace.action = 19;
                    err = os_file( &fileplace );
                    overflowing_free( fileplace.name );
                    DEBUG((" not found\n"));
                    continue;
                }

                overflowing_free( fileplace.name );

                context->selections_objecttype = fileplace.action;
                context->selections_size = fileplace.start;
                context->selections_loadaddr = fileplace.loadaddr;
                context->selections_execaddr = fileplace.execaddr;
                context->selections_attributes = fileplace.end;
            } /* end if (nested_filenames == NULL) */

            objecttype = objecttype_of_next_node( (search_handle)context );

            if ( objecttype == ObjectType_NotFound )
            {
                /*
                    Didn't find that, so go around
                    for another time - nothing to do here
                */
                DEBUG(("not found "));

                context->action = Next_Leaf;
            }
            else if ( context->recursive &&
                  is_a_directory( context, objecttype ) )
            {
                /*
                    If we are returning directories first, then
                    we have resolved it at this level
                */
                DEBUG(("directory "));
                resolved = context->directories_first;

                context->action = Nest_Into_Directory;
            }
            else
            {
                /*
                    Found a file or we found a directory
                    when not recursing, in which case
                    we've found something worth while and
                    so we've resolved things.
                */
                resolved = Yes;
                context->action = Next_Leaf;
                DEBUG(("resolved "));

            }
        }
        break;

        case Read_Next_Cache_Full: DEBUG(("Read_Next_Cache_Full "));
        /*
            If run out of entries in this directory
        */
        if ( context->nested_filenames->offset_to_next_item == -1 )
        {
            #ifdef USE_PROGRESS_BAR
            search_nest_level *nf = context->nested_filenames;
            unsigned int p = 0;

            /*
               Compensate for rounding errors by adding the 'spare' progress
               Isn't totally accurate if copying, as the actual progress values
               used will have been halved.
            */
            p = nf->total_progress - (nf->total_entries * nf->progress_per_object);
            if (p > 0) *progress += p;
            DEBUG(("finished %d entries (total %08x) extra progress +%08x", nf->total_entries, nf->total_progress, p));
            #else
            IGNORE(progress);
            #endif

            /*
                Down down one level
            */
            temp_context = context->nested_filenames->next_search_nest_level;
            for ( i = 0; i < Directory_Buffer_Size; i++ )
            {
                overflowing_free( context->nested_filenames->directory_buffer[ i ].object_name );
            }
            overflowing_free( context->nested_filenames );
            context->nested_filenames = temp_context;

            /*
                Return the directory after all the files in it
            */
            resolved = context->directories_last;

            context->action = Next_Leaf;
        }
        else
        {
            char **filename_store;

            /*
                Read more of this directory
            */
            gbpbplace.action      = 10;

            err = next_filename( context, context->nested_filenames->next_search_nest_level, (char **)&gbpbplace.file_handle );

            if ( err )
                continue;

            DEBUG(("%s ", (char *)gbpbplace.file_handle));


            #ifdef USE_PROGRESS_BAR
            search_nest_level *nf = context->nested_filenames;

            if (nf->total_entries == -1)
            {
                nf->total_entries = count_objects_in_dir((char *)gbpbplace.file_handle);

                if (nf->total_entries > 0)
                {
                  nf->progress_per_object = nf->total_progress / nf->total_entries;
                }
                else
                {
                  void *ref;

                  /*
                     Modify progress values so that half the progress for the dir is
                     added when the dir is finished (in the rounding process above)
                     and the other half when written. We have to do this now as when
                     the dir was added to the chain, we didn't know it was empty.
                     Of course if the dir is not in the chain, we are not copying, and
                     so we add all the progress for this dir at once.
                  */

                  if (nf->next_search_nest_level == NULL)
                  {
                    /* top level	*/
                    ref = context->chain_ref;
                  }
                  else
                  {
                    int i = nf->next_search_nest_level->next_entry_to_return;
                    ref = nf->next_search_nest_level->directory_buffer[i].chain_ref;
                  }

                  if (ref != NULL)
                  {
                    nf->total_progress /= 2;
                    nf->progress_per_object = 0;
                    modify_chain_file_progress(ref, nf->total_progress);
                  }

                } /* end if (total_entries > 0) */

                DEBUG(("%d entries %08x total progress %08x per object ", nf->total_entries, nf->total_progress, nf->progress_per_object));

            } /* end if (total_entries == -1) */

            #endif

            gbpbplace.data_addr   = &temp_directory_buffer;
            gbpbplace.number      = Directory_Buffer_Size;
            gbpbplace.seq_point   = context->nested_filenames->offset_to_next_item;
            gbpbplace.buf_len     = Temp_DirBuffer_Size;
            gbpbplace.wild_fld    = "*";

            err = os_gbpb( &gbpbplace );
            overflowing_free( (void *)gbpbplace.file_handle );

            if ( err )
            {
                if ( (err->errnum & FileError_Mask) == ErrorNumber_NotFound )
                {
                    /*
                            Cancel the error
                    */
                    err = NULL;

                    /*
                         Down down one level
                    */
                    temp_context = context->nested_filenames->next_search_nest_level;
                    for ( i = 0; i < Directory_Buffer_Size; i++ )
                    {
                            overflowing_free( context->nested_filenames->directory_buffer[ i ].object_name );
                    }
                    overflowing_free( context->nested_filenames );
                    context->nested_filenames = temp_context;

                    /*
                            Don't return the directory, as it
                            doesn't exist!
                    */

                    context->action = Next_Leaf;
                }

                continue;
            }

            for ( i = 0, pos = 0; i < gbpbplace.number; i++ )
            {
                context->nested_filenames->directory_buffer[ i ].load_address      = *(int *)&temp_directory_buffer[ pos ];
                pos += 4;
                context->nested_filenames->directory_buffer[ i ].execution_address = *(int *)&temp_directory_buffer[ pos ];
                pos += 4;
                context->nested_filenames->directory_buffer[ i ].length            = *(int *)&temp_directory_buffer[ pos ];
                pos += 4;
                context->nested_filenames->directory_buffer[ i ].attributes        = *(int *)&temp_directory_buffer[ pos ];
                pos += 4;
                context->nested_filenames->directory_buffer[ i ].object_type       = *(int *)&temp_directory_buffer[ pos ];
                pos += 4;

                /*
                    Free the filename if there's one there
                */
                filename_store = &context->nested_filenames->directory_buffer[ i ].object_name;
                if ( *filename_store )
                {
                    overflowing_free ( *filename_store );
                    *filename_store = NULL;
                }

                /*
                    Allocate some space for the file name
                */
                if ( ( *filename_store = overflowing_malloc( strlen( &temp_directory_buffer[ pos ] ) + 1 ) ) == NULL )
                {
                    /*
                            If the allocation failed, free everything up
                    */
                    int j;

                    for ( j = 0; j < i; j++ )
                    {
                            overflowing_free( context->nested_filenames->directory_buffer[ j ].object_name );
                            context->nested_filenames->directory_buffer[ j ].object_name = NULL;
                    }

                    err = error( mb_malloc_failed );

                    break;
                }

                strcpy( *filename_store, &temp_directory_buffer[ pos ] );

                pos += strlen( &temp_directory_buffer[ pos ] ) + 1;

                /* round pos up to a word boundary */
                pos = (( pos + 3 ) / 4 ) * 4;
            }

            if ( err )
                break;

            context->nested_filenames->offset_to_next_item = gbpbplace.seq_point;
            context->nested_filenames->entries_in_buffer = gbpbplace.number;
            context->nested_filenames->next_entry_to_return = -1;

            context->action = Next_Leaf;
        }
        break;

        case Nest_Into_Directory: DEBUG(("Nest_Into_Directory "));
        /*
            Go down into the next nesting level
        */

        temp_context = context->nested_filenames;

        context->nested_filenames = overflowing_malloc( sizeof( search_nest_level ));
        if ( context->nested_filenames == NULL )
        {
            context->nested_filenames = temp_context;
            err = error( mb_malloc_failed );
            continue;
        }

        context->nested_filenames->next_search_nest_level = temp_context;
        context->nested_filenames->offset_to_next_item = 0;
        context->nested_filenames->entries_in_buffer = 0;
        context->nested_filenames->next_entry_to_return = -1;

        #ifdef USE_PROGRESS_BAR
        if (temp_context != NULL)
        {
          context->nested_filenames->total_progress = temp_context->progress_per_object;
        }
        else
        {
          context->nested_filenames->total_progress = context->progress_per_object;
        }
        /* Default values, will be overwitten if this object is a dir */
        context->nested_filenames->progress_per_object = 0;
        context->nested_filenames->total_entries = -1;
        #endif

        for ( i = 0; i < Directory_Buffer_Size; i++ )
        {
            context->nested_filenames->directory_buffer[ i ].object_name = NULL;
            #ifdef USE_PROGRESS_BAR
            context->nested_filenames->directory_buffer[ i ].chain_ref = NULL;
            #endif
        }

        context->action = Next_Leaf;

        break;

        default:  /* disaster!!!!! */
        err = error( mb_unexpected_state );
        break;
        } /* end switch */

        DEBUG(("\n"));

    } /* end while */

    return err;
}


#ifdef USE_PROGRESS_BAR
void listfiles_convert_to_copymove( search_handle handle )
{
  search_context *context = (search_context *)handle;

  if (context == NULL) return;

  context->total_progress /= 2;
}
#endif