Is it possible to use Addressables like File and Directory from System.IO?

Sorry in advance if something like this already exists, but I have been trying to find a similar solution but can not find one that meets my need.

Take a look of my Addressables structure that I will use as an example here:

And all I want is some simple functions like Addressables.PathExists(string path), Addressables.DirectoryExists(string directory), Addressables.GetFilePathsFromDirectory(string directory). So if I call Addressables.DirectoryExists(“TimeTraveler”) or Addressables.DirectoryExists(“TimeTraveler/Mellifluous”), they will return true, because they do exist as keys in the Addressable system.

And if I call Addressables.GetFilePathsFromDirectory(“TimeTraveler/Mellifluous/SongInfo/StoryBoard@hard/Images/Images”), I can get a List contains “TimeTraveler/Mellifluous/SongInfo/StoryBoard@hard/Images/Images/test.png” and “TimeTraveler/Mellifluous/SongInfo/StoryBoard@hard/Images/Images/Esit.png”.

The closest thing I can think of now is that I will label everything in Addressables with some kind of “Everything” label, called LoadResourceLocations(“Everything”), and then do some parsing and create a custom Manager for this purpose, but before I fall into the rabbit hole, I just want to make sure I am recreating a solution if one already exists…

Okay, now, I totally understand the preferred workflow for Addressables is to use labels. However, my main point is that if Addressables is structured like Directory, looks like Directory, and feels like Directory, and we can load the Assets through the keys that are exactly like file path in normal OS (ex Addressables.LoadAssetAsync(“TimeTraveler/…/chart.txt”)), then my instinct would tell me that I can use it somehow like Directory as in System.IO. But it is just not something I can find via Google nor on this forum so far…

Again, sorry in advance if this feature or a solution already exists somewhere. Either way, please enlighten me a way out of this.

Thank you!

After some time, I still decide to write my own Parser for this.

Generic Directory Path Library

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;


public class GenericDirectoryPathLibrary
{
    internal class DirectoryInfoHolder
    {
        HashSet<string> ChildrenFiles = new HashSet<string>();
        HashSet<string> ChildrenDirectories = new HashSet<string>();

        public void AddToChildrenDirectoies(string path)
        {
            if (ChildrenDirectories.Contains(path) == false)
            {
                ChildrenDirectories.Add(path);
            }
        }

        public void AddToChildrenFiles(string path)
        {
            if(ChildrenFiles.Contains(path) == false)
            {
                ChildrenFiles.Add(path);
            }
        }

        public HashSet<string> GetChildrenFiles()
        {
            return new HashSet<string>(ChildrenFiles);
        }

        public HashSet<string> GetChildrenDirectories()
        {
            return new HashSet<string>(ChildrenDirectories);
        }
    }

    private Dictionary<string, DirectoryInfoHolder> DirectoryInfoHolderDictionary = new Dictionary<string, DirectoryInfoHolder>();
    private HashSet<string> AllPaths = new HashSet<string>();
    private HashSet<string> FilePaths = new HashSet<string>();

    /// <summary>
    /// A Path is recognized as a "FilePath" if it contains an extension at the end. Or else it is considered to be a "DirectoryPath"
    /// </summary>
    /// <param name="paths"></param>
    public GenericDirectoryPathLibrary(HashSet<string> paths)
    {
        AllPaths = new HashSet<string>(paths);
        ParsePaths();
    }

    public void ParsePaths()
    {
        DirectoryInfoHolderDictionary.Clear();

        foreach (var path in AllPaths)
        {
            if (path.Length == 0)
            {
                continue;
            }

            if (IsDirectoryPath(path))
            {
                AddDirectoryPathToDictionary(path);
            }
            else if(IsFilePath(path))
            {
                FilePaths.Add(path);

                var ParentDirectoryPath = Path.GetDirectoryName(path);

                //ignore empty path; happens when the path is just the file name
                if(ParentDirectoryPath.Length > 0)
                {
                    AddDirectoryPathToDictionary(ParentDirectoryPath);
                }

                DirectoryInfoHolderDictionary[ParentDirectoryPath].AddToChildrenFiles(path);
            }
        }
    }

    private void AddDirectoryPathToDictionary(string directoryPath)
    {
        if (DirectoryInfoHolderDictionary.ContainsKey(directoryPath) == false)
        {
            DirectoryInfoHolderDictionary.Add(directoryPath, new DirectoryInfoHolder());

            var ParentPath = Path.GetDirectoryName(directoryPath);

            if(ParentPath.Length > 0)
            {
                AddDirectoryPathToDictionary(ParentPath);
                DirectoryInfoHolderDictionary[ParentPath].AddToChildrenDirectoies(directoryPath);
            }
        }
    }

    private bool IsDirectoryPath(string path)
    {
        return Path.GetExtension(path).Length == 0;
    }

    private bool IsFilePath(string path)
    {
        return Path.GetExtension(path).Length > 0;
    }

    public bool DirectoryPathExists(string directoryPath)
    {
        return DirectoryInfoHolderDictionary.ContainsKey(directoryPath);
    }

    public bool FilePathExists(string path)
    {
        return FilePaths.Contains(path);
    }

    public bool PathExists(string path)
    {
        if (DirectoryPathExists(path))
        {
            return true;
        }
        else
        {
            // AllPaths contain file paths, which is the only known source of file paths in the library
            return AllPaths.Contains(path);
        }
    }

    /// <summary>
    /// return all the sub-directories paths that belong to this directoryPath
    /// </summary>
    /// <param name="directoryPath"></param>
    /// <param name="includeSubDirectories">if set to true, do a recursive search return the all the sub-directories paths</param>
    /// <returns></returns>
    public HashSet<string> GetChildrenDirectoriesPaths(string directoryPath, bool includeSubDirectories = false)
    {
        if(DirectoryInfoHolderDictionary.ContainsKey(directoryPath) == false)
        {
            return new HashSet<string>();
        }

        HashSet<string> Result = DirectoryInfoHolderDictionary[directoryPath].GetChildrenDirectories();

        if (includeSubDirectories)
        {
            foreach(var childrenDirectoryPath in DirectoryInfoHolderDictionary[directoryPath].GetChildrenDirectories())
            {
                if (DirectoryInfoHolderDictionary.ContainsKey(childrenDirectoryPath))
                {
                    Result.UnionWith(this.GetChildrenDirectoriesPaths(childrenDirectoryPath, true));
                }
            }
        }

        return Result;
    }

    /// <summary>
    /// return all the sub-file paths that belong to this directoryPath
    /// </summary>
    /// <param name="directoryPath"></param>
    /// <param name="includeSubDirectories">if set to true, do a recursive search return the all the sub-file paths</param>
    /// <returns></returns>
    public HashSet<string> GetChildrenFilePaths(string directoryPath, bool includeSubDirectories = false)
    {
        if (DirectoryInfoHolderDictionary.ContainsKey(directoryPath) == false)
        {
            return new HashSet<string>();
        }

        HashSet<string> Result = DirectoryInfoHolderDictionary[directoryPath].GetChildrenFiles();

        if (includeSubDirectories)
        {
            HashSet<string> SubDirectories = GetChildrenDirectoriesPaths(directoryPath, true);

            foreach(var directroy in SubDirectories)
            {
                if (DirectoryInfoHolderDictionary.ContainsKey(directroy))
                    Result.UnionWith(DirectoryInfoHolderDictionary[directroy].GetChildrenFiles());
            }
        }

        return Result;
    }

    /// <summary>
    /// return all the paths (directories and files) in the directoryPath
    /// </summary>
    /// <param name="directoryPath"></param>
    /// <param name="includeSubDirectories">if set to true, do a recursive search return the all the sub-file paths</param>
    /// <returns></returns>
    public HashSet<string> GetChildrenPaths(string directoryPath, bool includeSubDirectories = false)
    {
        if (DirectoryInfoHolderDictionary.ContainsKey(directoryPath) == false)
        {
            return new HashSet<string>();
        }

        HashSet<string> Result = DirectoryInfoHolderDictionary[directoryPath].GetChildrenDirectories();
        Result.UnionWith(DirectoryInfoHolderDictionary[directoryPath].GetChildrenFiles());

        if (includeSubDirectories)
        {
            Result.UnionWith(GetChildrenFilePaths(directoryPath, true));
            Result.UnionWith(GetChildrenDirectoriesPaths(directoryPath, true));
        }

        return Result;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using Cysharp.Threading.Tasks;
using System.Linq;

public static class AddressablePathLookUp
{
    private static bool HasLoaded = false;

    private static GenericDirectoryPathLibrary AddressableAddressLibrary;

    /// <summary>
    /// Call this externally to reload the library for whatever reason you want
    /// </summary>
    /// <returns></returns>
    public static async UniTask ForceReloadLibrary()
    {
        HasLoaded = false;
        await LoadAddresses();
    }

    private static async UniTask LoadAddresses()
    {
        if(HasLoaded == false)
        {
            var Addresses = await Addressables.LoadResourceLocationsAsync("Everything");
            HashSet<string> Result = new HashSet<string>(Addresses.Select(x => x.PrimaryKey));

            AddressableAddressLibrary = new GenericDirectoryPathLibrary(Result);

            Addressables.Release(Addresses);

            HasLoaded = true;
        }
    }

    public async static UniTask<bool> AddressablePathExistsAsync(string address)
    {
        await LoadAddresses();
        return AddressableAddressLibrary.PathExists(address);
    }

    public async static UniTask<List<string>> GetAllFilePathsInDirectoryAddress(string directoryAddress)
    {
        await LoadAddresses();
        return AddressableAddressLibrary.GetChildrenFilePaths(directoryAddress, false).ToList();
    }
}

Label everything in your Addressables with the “Everything” label, and you can call AddressablePathExistsAsync(string address) and GetFilePathsInDirectoryAddress(string directoryAddress) where ever you want.