[AD] al_for_each_file and al_for_each_fs_entry

[ Thread Index | Date Index | More lists.liballeg.org/allegro-developers Archives ]


As this was requested on the forum and several liked the idea, here is
a documented implementation of al_for_each_file and
al_for_each_fs_entry, with examples added to ex_dir.c.
I hope this is acceptable, if not I will improve it.

Kind regards,

Bjorn.
diff --git a/docs/src/refman/fshook.txt b/docs/src/refman/fshook.txt
index 1aaa5b2..81df80b 100644
--- a/docs/src/refman/fshook.txt
+++ b/docs/src/refman/fshook.txt
@@ -27,6 +27,29 @@ Filesystem modes/types
 * ALLEGRO_FILEMODE_ISFILE - Regular file
 * ALLEGRO_FILEMODE_ISDIR - Directory
 
+## API: ALLEGRO_FOR_EACH_FILE_FLAGS
+
+This enum contain flags that control the operation of [al_for_each_file]
+and [al_for_each_fs_entry]. 
+
+* ALLEGRO_FOR_EACH_FILE_RECURSE - Recurse into directories
+* ALLEGRO_FOR_EACH_FILE_BASENAME - Call the callback with the basename
+  of the file in stead of the full absolute path only for [al_for_each_file].  
+* ALLEGRO_FOR_EACH_FILE_FILTER - Filter out files that don't match one of
+  [ALLEGRO_FILE_MODE] flags which should e binary orred with this flag.
+
+## API: ALLEGRO_FOR_EACH_FILE_RESULTS
+
+Thi enum contails the list of values that [al_for_each_file]
+and [al_for_each_fs_entry] and their callback functions may return.
+ 
+* ALLEGRO_FOR_EACH_FILE_ERROR - An error occured, iteration was halted.
+  Use [al_get_errno] to get an error code that details the problem. The error
+  code can be one of +ENOMEM+, +ENOTDIR+ or +ENOENT+. 
+* ALLEGRO_FOR_EACH_FILE_STOP - Return this value from the callback to top
+  iteration without there being an error.
+* ALLEGRO_FOR_EACH_FILE_OK - Return this value to continue iterating.
+
 ## API: al_create_fs_entry
 
 Creates an [ALLEGRO_FS_ENTRY] object pointing to path on the filesystem.
@@ -210,6 +233,109 @@ Returns the handle on success, NULL on error.
 
 See also: [al_fopen]
 
+### API: al_for_each_fs_entry
+
+This convenience function looks up all entries under +dir+, and then
+will call the callback function +callback+ once iteratively for every filesystem
+entry in that directory. Note that the +.+ and +..+ directories will never show
+up, they are filtered away automatically.
+
+The +callback+ will be called with a +entry+ pointing to an [ALLEGRO_FS_ENTRY]
+that matches one file under +dir+, and the pointer +extra+ as it was passed
+to [al_for_each_fs_entry]. This +extra+ parameter is useful for storing
+results or hooking your own extra behavior to [al_for_each_fs_entry]. 
+
+When +callback+ returns +ALLEGRO_FOR_EACH_FILE_OK+ then iteration will contine
+with the next file system entry. However if +callback+ returns
++ALLEGRO_FOR_EACH_FILE_STOP+ or +ALLEGRO_FOR_EACH_FILE_ERROR+, then iteration
+will stop.
+
+Note that the +entry+ parameter of +callback+ will be destroyed automatically
+when +callback+ returns so if you want to keep it you will need to make
+a duplicate and store that somewhere yourself.
+
+The flags may be 0 or a combination of one or more [ALLEGRO_FOR_EACH_FILE_FLAGS]
+and [ALLEGRO_FILE_MODE] binary OR'd together.
+
+If the flags have +ALLEGRO_FOR_EACH_FILE_RECURSE+ set then
+[al_for_each_fs_entry]  will recurse into all directories it finds
+in +dir+ and sub direcories of it.
+
+The flag +ALLEGRO_FOR_EACH_FILE_BASENAME+ has no effect for
+[al_for_each_fs_entry], it's for use by [al_for_each_file].
+
+If the flag ALLEGRO_FOR_EACH_FILE_FILTER is set, then you should binary OR it
+with one or more [ALLEGRO_FILE_MODE] flags. Filesystem entries of which the
+[al_get_fs_entry_mode] doesn't match the [ALLEGRO_FILE_MODE] flags ORrred into
++flags+ will be filtered out in that +callback+ will not be called for them.
+However, this has no effect on recursive iteration which will visit even
+directories that are being filtered out.
+ 
+This function returns +ALLEGRO_FOR_EACH_FILE_OK+ if it sucessfully
+iterated over all requested entries of +dir+. It returns
++ALLEGRO_FOR_EACH_FILE_STOP+ if the +callback+ requested to stop iteration
+by returning this value. If something went wrong along the way
++ALLEGRO_FOR_EACH_FILE_ERROR+ will be returned and [al_get_errno] will return
+one of ENOMEM, ENOENT or ENOTDIR to indicate more in detail what went wrong.
+
+See also: [al_for_each_filename]
+
+### API: al_for_each_file
+
+AL_FUNC(bool,  al_for_each_file, (
+         const char *path,
+         bool (*callback)(const char * filename, int mode, void *extra),
+         int flags,
+         void *extra));
+
+
+This convenience function looks up all entries under the directory +dir+, and
+then will call the callback function +callback+ once iteratively for every
+filesystem entry in that directory. Note that the +.+ and +..+ directories will
+never show up, they are filtered away automatically.
+
+The +callback+ will be called with a +filename+ pointing to an
+string with the filename that matches one file under +dir+. The +callback+ a
+also receives a +mode+ parammeter which is the [al_get_fs_entry_name] of the
+corresponding file, and  the pointer +extra+ as it was passed
+to [al_for_each_fs_entry]. This +extra+ parameter is useful for storing
+results or hooking your own extra behavior to [al_for_each_fs_entry].
+
+When +callback+ returns +ALLEGRO_FOR_EACH_FILE_OK+ then iteration will contine
+with the next file system entry. However if +callback+ returns
++ALLEGRO_FOR_EACH_FILE_STOP+ or +ALLEGRO_FOR_EACH_FILE_ERROR+, then iteration
+will stop.
+
+Note that the +filename+ parameter of +callback+ will be destroyed automatically
+when +callback+ returns so if you want to keep it you will need to make a
+duplicate and store that somewhere yourself.
+
+The flags may be 0 or a combination of one or more [ALLEGRO_FOR_EACH_FILE_FLAGS]
+and [ALLEGRO_FILE_MODE] binary OR'd together.
+
+If the flags have +ALLEGRO_FOR_EACH_FILE_RECURSE+ set then
+[al_for_each_file]  will recurse into all directories it finds
+in +dir+ and sub direcories of it.
+
+If the flag +ALLEGRO_FOR_EACH_FILE_BASENAME+ is set, then filename will contain
+the basename of the file name, that is, the file name without the full
+directory.
+
+If the flag ALLEGRO_FOR_EACH_FILE_FILTER is set, then you should binary OR it
+with one or more [ALLEGRO_FILE_MODE] flags. Files of which the +mode+ doesn't
+match the [ALLEGRO_FILE_MODE] flags ORrred into +flags+ will be filtered out in
+that +callback+ will not be called for them. However, this has no effect on
+recursive iteration which will visit even directories that are being filtered
+out.
+ 
+This function returns +ALLEGRO_FOR_EACH_FILE_OK+ if it sucessfully
+iterated over all requested entries of +dir+. It returns
++ALLEGRO_FOR_EACH_FILE_STOP+ if the +callback+ requested to stop iteration
+by returning this value. If something went wrong along the way
++ALLEGRO_FOR_EACH_FILE_ERROR+ will be returned and [al_get_errno] will return
+one of ENOMEM, ENOENT or ENOTDIR to indicate more in detail what went wrong.
+
+See also: [al_for_each_fs_entry]
 
 ## Alternative filesystem functions
 
diff --git a/examples/ex_dir.c b/examples/ex_dir.c
index 32623d3..17d40cf 100644
--- a/examples/ex_dir.c
+++ b/examples/ex_dir.c
@@ -53,6 +53,37 @@ static void print_entry(ALLEGRO_FS_ENTRY *entry)
    al_close_directory(entry);
 }
 
+
+static bool print_directory_callback(const char * filename, int mode, void * extra) {
+   log_printf("%s: (%d) %s\n", (char *) extra,  mode, filename);
+   return true;
+}
+
+static void print_directory(char * dirname)
+{
+   log_printf("\n------------------------------------\nExample of al_for_each_filename:\n\n");   
+   al_for_each_file(dirname, print_directory_callback,
+   ALLEGRO_FOR_EACH_FILE_SHORT_NAME |
+   ALLEGRO_FILEMODE_ISFILE |
+   ALLEGRO_FOR_EACH_FILE_FILTER |
+   ALLEGRO_FOR_EACH_FILE_RECURSE,
+   (void*) dirname);
+}
+
+
+static bool print_fs_entry_callback(ALLEGRO_FS_ENTRY * entry, void * extra) {
+   (void) extra;
+   print_file(entry);
+   return true;
+}
+
+static void print_fs_entry(ALLEGRO_FS_ENTRY * dir)
+{
+   log_printf("\n------------------------------------\nExample of al_for_each_fs_entry:\n\n");   
+   al_for_each_fs_entry(dir, print_fs_entry_callback, 0, (void*) al_get_fs_entry_name(dir));
+}
+
+
 int main(int argc, char **argv)
 {
    int i;
@@ -62,7 +93,7 @@ int main(int argc, char **argv)
    }
    open_log_monospace();
    
-   log_printf("%-36s %-6s %8s %8s %8s %8s\n",
+   log_printf("Example of filesystem entry functions:\n\n%-36s %-6s %8s %8s %8s %8s\n",
       "name", "flags", "ctime", "mtime", "atime", "size");
    log_printf(
       "------------------------------------ "
@@ -75,13 +106,19 @@ int main(int argc, char **argv)
    if (argc == 1) {
       ALLEGRO_FS_ENTRY *entry = al_create_fs_entry("data");
       print_entry(entry);
+      print_fs_entry(entry);
       al_destroy_fs_entry(entry);
+      print_directory("data");
+      print_directory("data");
+      print_directory("data");
    }
 
    for (i = 1; i < argc; i++) {
       ALLEGRO_FS_ENTRY *entry = al_create_fs_entry(argv[i]);
       print_entry(entry);
+      print_fs_entry(entry);
       al_destroy_fs_entry(entry);
+      print_directory(argv[i]);
    }
 
    close_log(true);
diff --git a/include/allegro5/fshook.h b/include/allegro5/fshook.h
index 004b0c4..9472bd2 100644
--- a/include/allegro5/fshook.h
+++ b/include/allegro5/fshook.h
@@ -115,6 +115,39 @@ AL_FUNC(bool,                 al_make_directory,   (const char *path));
 AL_FUNC(ALLEGRO_FILE *,       al_open_fs_entry,    (ALLEGRO_FS_ENTRY *e,
                                                     const char *mode));
 
+/* Utility functions and callbacks for them. */
+
+/* Enum: ALLEGRO_FOR_EACH_FILE_FLAGS
+ */
+typedef enum ALLEGRO_FOR_EACH_FILE_FLAGS
+{
+   ALLEGRO_FOR_EACH_FILE_RECURSE       = 1 << 8,
+   ALLEGRO_FOR_EACH_FILE_SHORT_NAME    = 1 << 9,
+   ALLEGRO_FOR_EACH_FILE_FILTER        = 1 << 10,
+} ALLEGRO_FOR_EACH_FILE_FLAGS;
+
+
+/* Enum: ALLEGRO_FOR_EACH_FILE_RESULTS
+ */
+typedef enum ALLEGRO_FOR_EACH_FILE_RESULTS
+{
+   ALLEGRO_FOR_EACH_FILE_ERROR       = -1,
+   ALLEGRO_FOR_EACH_FILE_STOP        = 0,
+   ALLEGRO_FOR_EACH_FILE_OK          = 1
+} ALLEGRO_FOR_EACH_FILE_FLAGS;
+
+
+AL_FUNC(int,  al_for_each_fs_entry, (
+         ALLEGRO_FS_ENTRY *dir,
+         int (*callback)(ALLEGRO_FS_ENTRY *e, void *extra),
+         int flags,
+         void *extra));
+
+AL_FUNC(int,  al_for_each_file, (
+         const char *path,
+         int (*callback)(const char * filename, int mode, void *extra),
+         int flags,
+         void *extra));
 
 /* Thread-local state. */
 AL_FUNC(const ALLEGRO_FS_INTERFACE *, al_get_fs_interface, (void));
diff --git a/src/fshook.c b/src/fshook.c
index 4cd6d69..ab3f25c 100644
--- a/src/fshook.c
+++ b/src/fshook.c
@@ -235,6 +235,128 @@ ALLEGRO_FILE *al_open_fs_entry(ALLEGRO_FS_ENTRY *e, const char *mode)
 }
 
 
+/* Utility functions and callbacks for them. */
+
+
+/* Halper to handle a single entry of al_for_each_entry */
+static int al_for_each_fs_entry_handle_entry(
+   ALLEGRO_FS_ENTRY *entry,
+   int (*callback)(ALLEGRO_FS_ENTRY *e, void *extra),
+   int flags,
+   void *extra)
+{
+   int result;
+   int mode    = al_get_fs_entry_mode(entry);
+   /* Handle recursion if requested. Do this before filtering so even
+    * filetered entries are recursed into.  This also ensures depth-first
+    * recursion.
+    */
+   if (mode & ALLEGRO_FILEMODE_ISDIR) {
+      if (flags & ALLEGRO_FOR_EACH_FILE_RECURSE) {
+         result = al_for_each_fs_entry(entry, callback, flags, extra);
+         if (result < ALLEGRO_FOR_EACH_FILE_OK) {
+            return result;
+         }
+      }
+   }
+   
+   /* Filter if requested. */
+   if (flags & ALLEGRO_FOR_EACH_FILE_FILTER) {
+      int filter  = flags & 0xff; /* Low bits contain filter. */
+      if (!(filter & mode)) return true;
+   }
+   
+   result = callback(entry, extra);
+
+   return result;
+}
+
+
+/* Function: al_for_each_fs_entry
+ */
+bool al_for_each_fs_entry (
+   ALLEGRO_FS_ENTRY *dir,
+   bool (*callback)(ALLEGRO_FS_ENTRY *e, void *extra),
+   int flags,
+   void *extra)
+{
+   ALLEGRO_FS_ENTRY * entry;
+   al_set_errno(0);
+   
+   if (!al_open_directory(dir)) {
+      al_set_errno(ENOENT);
+      return ALLEGRO_FOR_EACH_FILE_ERROR;
+   }
+      
+   entry = al_read_directory(dir);
+   while(entry) {
+      int result = al_for_each_fs_entry_handle_entry(entry, callback, flags, extra);
+      al_destroy_fs_entry(entry);
+      if (result < ALLEGRO_FOR_EACH_FILE_OK) {
+         return result;
+      }
+      entry = al_read_directory(dir);
+   }
+
+   return ALLEGRO_FOR_EACH_FILE_OK;
+}
+
+typedef struct ALLEGRO_FOR_EACH_FILE_INFO {
+   int (*callback)(const char * filename, int mode, void *extra);
+   int flags;
+   void * extra;
+   const char * path;
+} ALLEGRO_FOR_EACH_FILE_INFO;
+
+
+/* Helper callback that will map al_for_each_fs_entry to
+ * for each file. */
+static int al_for_each_file_callback_wrapper(ALLEGRO_FS_ENTRY * e, void * extra) {
+   ALLEGRO_FOR_EACH_FILE_INFO * info = extra;
+   int result;
+   const char * name = al_get_fs_entry_name(e);
+   int mode          = al_get_fs_entry_mode(e);
+   if (info->flags & ALLEGRO_FOR_EACH_FILE_SHORT_NAME) {
+      ALLEGRO_PATH * path = al_create_path(name);
+      if (!path) {
+         al_set_errno(ENOMEM);
+         return AL_FOR_EACH_FILE_ERROR;
+      }
+      result = info->callback(al_get_path_basename(path), mode, info->extra);
+      al_destroy_path(path);
+   } else  {
+      result = info->callback(name, mode, info->extra);
+   }
+   return result;
+}
+
+/* Function: al_for_each_file
+ */
+int al_for_each_file(
+   const char *path,
+   int (*callback)(const char * filename, int mode, void *extra),
+   int flags,
+   void *extra)
+{
+   ALLEGRO_FOR_EACH_FILE_INFO info;
+   ALLEGRO_FS_ENTRY * dir;
+   int result;
+   dir            = al_create_fs_entry(path);
+   if (!dir) {
+      al_set_errno(ENOENT);
+   }
+   info.callback  = callback;
+   info.extra     = extra;
+   info.flags     = flags;
+   info.path      = path;
+   result         = al_for_each_fs_entry(dir, al_for_each_file_callback_wrapper,
+                     flags, &info);
+   al_destroy_fs_entry(dir);
+   return result;
+}
+
+
+
 /*
  * Local Variables:
  * c-basic-offset: 3


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/