How to recursively get all file names from a given folder using Gio.File?

I have an extension gdm-extension GitHub - PRATAP-KUMAR/gdm-extension: GDM Extension - Tweak few things of GDM Login Screen from the login screen itself.

Recently I got a request to add a feature for choosing fonts, same as shell-themes and icon-themes from the above extension.

For this I need to get all .ttf and .otf file names from two folders
[/usr/local/share/fonts, /usr/share/fonts]

after searching for 2 days, I decided to use this documentation. File Operations | GNOME JavaScript

when I try to use the code it as it is to delete files, It seems to work.

Gio.IOErrorEnum: Error removing file /usr/share/fonts/cantarell/Cantarell-VF.otf: Permission denied

Stack trace:
  recursiveDeleteCallback@file:///home/admin/.local/share/gnome-shell/extensions/recursive/extension.js:50:18
  recursiveFileOperation@file:///home/admin/.local/share/gnome-shell/extensions/recursive/extension.js:104:44
  async*recursiveDeleteCallback@file:///home/admin/.local/share/gnome-shell/extensions/recursive/extension.js:55:20
  recursiveFileOperation@file:///home/admin/.local/share/gnome-shell/extensions/recursive/extension.js:104:44
  async*myPatch@file:///home/admin/.local/share/gnome-shell/extensions/recursive/extension.js:143:19
  enable@file:///home/admin/.local/share/gnome-shell/extensions/recursive/extension.js:128:14
  _callExtensionEnable@resource:///org/gnome/shell/ui/extensionSystem.js:266:38
  _enableAllExtensions@resource:///org/gnome/shell/ui/extensionSystem.js:798:24
  async*_sessionUpdated@resource:///org/gnome/shell/ui/extensionSystem.js:827:20
  async*ExtensionManager/<@resource:///org/gnome/shell/ui/extensionSystem.js:48:18
  _callHandlers@resource:///org/gnome/gjs/modules/core/_signals.js:130:42
  _emit@resource:///org/gnome/gjs/modules/core/_signals.js:119:10
  _sync@resource:///org/gnome/shell/ui/sessionMode.js:207:14
  popMode@resource:///org/gnome/shell/ui/sessionMode.js:178:14
  _continueDeactivate@resource:///org/gnome/shell/ui/screenShield.js:543:30
  deactivate/<@resource:///org/gnome/shell/ui/screenShield.js:534:44
  finish@resource:///org/gnome/shell/gdm/authPrompt.js:688:13
  finish@resource:///org/gnome/shell/ui/unlockDialog.js:882:26
  deactivate@resource:///org/gnome/shell/ui/screenShield.js:534:26
  _getLoginSession/<@resource:///org/gnome/shell/ui/screenShield.js:152:24
  _callHandlers@resource:///org/gnome/gjs/modules/core/_signals.js:130:42
  _emit@resource:///org/gnome/gjs/modules/core/_signals.js:119:10
  _convertToNativeSignal@resource:///org/gnome/gjs/modules/core/overrides/Gio.js:152:19
  @resource:///org/gnome/shell/ui/init.js:21:20
  
Gio.IOErrorEnum: Error removing file /usr/share/fonts/encodings/viscii1.1-1.enc.gz: Permission denied

This line from above, indicates that the recursion is working
Gio.IOErrorEnum: Error removing file /usr/share/fonts/cantarell/Cantarell-VF.otf: Permission denied

When I replace file.delete(cancellable) with names.push(file) nothing working.

Please help.

Please show the actual code you’re working with.

Thank you for your response @andyholmes.

I managed to get it to work just few hours ago. Here is the code.

import GLib from 'gi://GLib';
import Gio from 'gi://Gio';

/* Gio.File */
Gio._promisify(Gio.File.prototype, 'copy_async');
Gio._promisify(Gio.File.prototype, 'create_async');
Gio._promisify(Gio.File.prototype, 'delete_async');
Gio._promisify(Gio.File.prototype, 'enumerate_children_async');
Gio._promisify(Gio.File.prototype, 'load_contents_async');
Gio._promisify(Gio.File.prototype, 'make_directory_async');
Gio._promisify(Gio.File.prototype, 'move_async');
Gio._promisify(Gio.File.prototype, 'open_readwrite_async');
Gio._promisify(Gio.File.prototype, 'query_info_async');
Gio._promisify(Gio.File.prototype, 'replace_contents_async');
Gio._promisify(Gio.File.prototype, 'replace_contents_bytes_async', 'replace_contents_finish');
Gio._promisify(Gio.File.prototype, 'trash_async');

/* Gio.FileEnumerator */
Gio._promisify(Gio.FileEnumerator.prototype, 'next_files_async');

/* Gio.InputStream */
Gio._promisify(Gio.InputStream.prototype, 'read_bytes_async');

/* Gio.OutputStream */
Gio._promisify(Gio.OutputStream.prototype, 'write_bytes_async');

/**
 * Callback signature for recursiveFileOperation().
 *
 * The example callback `recursiveDeleteCallback()` demonstrates how to
 * recursively delete a directory of files, while skipping unsupported file types.
 *
 * @param {Gio.File} file - the file to operate on
 * @param {Gio.FileType} fileType - the file type
 * @param {Gio.Cancellable} [cancellable] - optional cancellable
 * @returns {Promise|null} a Promise for the operation, or %null to ignore
 */

const recursiveDeleteCallback = async (file, fileType, cancellable = null, array) => {
    switch (fileType) {
    case Gio.FileType.REGULAR:
    case Gio.FileType.SYMBOLIC_LINK: {
        const fileInfo = await file.query_info_async('standard::*', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, GLib.PRIORITY_DEFAULT, null);
        array.push(fileInfo.get_name());
        return;
    }

    case Gio.FileType.DIRECTORY: {
        return recursiveFileOperation(file, recursiveDeleteCallback, cancellable, array);
    }

    default:
        return null;
    }
};

/**
 * Recursively operate on @file and any children it may have.
 *
 * @param {Gio.File} file - the file or directory to delete
 * @param {Function} callback - a function that will be passed the file,
 *     file type (e.g. regular, directory), and @cancellable
 * @param {Gio.Cancellable} [cancellable] - optional cancellable
 * @param {object} array - array to hold font file names
 * @returns {Promise} a Promise for the operation
 */
async function recursiveFileOperation(file, callback, cancellable = null, array) {
    const fileInfo = await file.query_info_async('standard::type',
        Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, GLib.PRIORITY_DEFAULT,
        cancellable);

    const fileType = fileInfo.get_file_type();

    // If @file is a directory, collect all the operations as Promise branches
    // and resolve them in parallel
    if (fileType === Gio.FileType.DIRECTORY) {
        const iter = await file.enumerate_children_async('standard::type',
            Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, GLib.PRIORITY_DEFAULT,
            cancellable);

        const branches = [];

        while (true) {
            // eslint-disable-next-line
            const fileInfos = await iter.next_files_async(100, GLib.PRIORITY_DEFAULT, cancellable);

            if (fileInfos.length === 0)
                break;

            for (const info of fileInfos) {
                const child = iter.get_child(info);
                const childType = info.get_file_type();

                // The callback decides whether to process a file, including
                // whether to recurse into a directory
                const branch = callback(child, childType, cancellable, array);

                if (branch)
                    branches.push(branch);
            }
        }

        await Promise.all(branches);
    }

    // Return the Promise for the top-level file
    return callback(file, cancellable, array);
}

export {recursiveFileOperation, recursiveDeleteCallback};

At line no 44, I tweaked it instead of file.delete(cancellable)
array.push(fileInfo.get_name());

1 Like