How to determine install and storage directories

My SDK will handle bootstrapping of the application (e.g. the entry point uses agera::application::start! and does not define any main function manually). This is necessary because the SDK should be able to export an application into a publishable APK or Windows installer and it should be possible for the developer to do things similiar to:

use agera::file::*;
File::new("app://some-file.txt").read_utf8();
File::new("app-storage://some-file.bin").write(content);

File itself is similiar to flash.filesystem.File from Adobe AIR. The app: and app-storage: schemes identify files in directories that are very inpredictable by Rust code, including because:

  • The Rust code does not know the application ID. For example, it could be com.microsoft.todo, com.nintendo.mariomaker.
  • These directories vary per operating system.
  • These directories, when debugging, will be inside the recipes directory (target), and not be the actual exported application installation's and storage's directories.

In respect to the file API, I'm aiming to support only Windows, Linux, OSX and Android for now. The web, too, will be supported, however I've no idea of how the synchronous operations will be done given that the origin-private file system API has no synchronous operations at all.

I'd like to know if anyone has any input on what I should use for Windows, Linux and OSX; is it data_dir() from dirs? I'm wondering if, e.g., appending the application ID to the native OS's data directory will be compatible with the Steam applications sandbox. To be honest, I've no idea of how the proper application directory is even determined by Steam.

So right now I've this in src/target.rs:

...

#[cfg(target_os = "android")]
pub use ::android_activity as activity;

#[cfg(target_os = "android")]
pub use ::jni;

#[cfg(target_os = "android")]
#[doc(hidden)]
pub static APPLICATION: Lazy<RwLock<Option<activity::AndroidApp>>> = Lazy::new(|| RwLock::new(None));

#[cfg(target_os = "android")]
pub fn application() -> activity::AndroidApp {
    APPLICATION.read().unwrap().as_ref().unwrap().clone()
}

...

I'll also read the application ID from the application descriptor and likely put it in agera::application::id().

Here's what I've of relevant in src/file/file.rs:

pub(crate) fn application_installation_directory() -> String {
    if_native_target! {{
        if cfg!(target_os = "android") {
            let path = if let Some(p) = crate::target::application().external_data_path() { p.to_string_lossy().into_owned() } else { crate::target::application().internal_data_path().unwrap().to_string_lossy().into_owned() };
            return FlexPath::new_common(&path).resolve(".install").to_string();
        } else {
            unsupported_platform!();
        }
    }}
    if_browser_target! {{
        unsupported_platform!();
    }}
}

pub(crate) fn application_storage_directory() -> String {
    if_native_target! {{
        if cfg!(target_os = "android") {
            let path = if let Some(p) = crate::target::application().external_data_path() { p.to_string_lossy().into_owned() } else { crate::target::application().internal_data_path().unwrap().to_string_lossy().into_owned() };
            return FlexPath::new_common(&path).resolve(".storage").to_string();
        } else {
            unsupported_platform!();
        }
    }}
    if_browser_target! {{
        unsupported_platform!();
    }}
}

Because those aren't "real", in the sense that they aren't an intrinsic part of an executable. I'd say it's more of an OS-/packaging-specific detail.

The question is kind of fundamentally flawed. At least on Windows, there is no single "storage directory". You have at least Roaming (these files should follow the user), Local (these files stay with the machine) and LocalLow (honestly don't understand this one well enough to explain it). Misusing them, from a user's perspective, is somewhere between irritating to infuriating.[1]

Then, on Linux, I believe you also have a collection of different locations based on what the files you're storing there are used for.

This is why dirs has so many functions: because they can't all be lumped together into a single "storage directory".

Also, on Windows, the expectation is to use the human-readable names for these folders, not an internal ID. Unless you're building a UWP app, which is a completely different kettle of bizarre space fish that I don't know anything about. [2]

Maybe instead of trying to expose a simple but wrong interface, you could expose all of the different locations as different roots. That would be complicated, but at least it's honest.

As for Steam: insofar as I'm aware, it doesn't sandbox anything on Windows. The only place where it might be sandboxing anything is on Linux depending on some matrix of "is running under Proton" and "is running inside Flatpak". There's also another, completely different path structure for cloud saves, so if you're supporting Steam games then the problem is even more complicated than it was before this paragraph. :slight_smile:

Actually, I just noticed: dirs doesn't expose the "Saved Games" folder on Windows. So even exposing everything in dirs isn't enough. To be fair, I don't even remember which of the multiple "Saved Games" folders on Windows is the "real" one any more. No one could ever seem to agree.

Aren't platform differences fun?!


  1. I recently spent half an hour digging tens of gigabytes of complete garbage out of my backup because programmers are complete [expletives deleted] who just shove whatever they damn well please into my profile folder. Do you know, I don't need to waste backup space on hundreds of [more expletives deleted] cache files from your [long and extremely angry tirade] because you [goodness gracious] and your [have you considered therapy] with a god damn pig! ↩︎

  2. UWP is, in many ways, a different target to "normal" Windows, but is still also Windows, and you'll probably need to support that. Or not. I believe Microsoft gave up on trying to force people to use UWP on the store at least, but who knows when they're going to change their minds again. ↩︎

3 Likes

Programs not handling this properly is a personal pet peeve of mine. Specifically configuration and state are supposed to go in different directories. I version control my config files with git, and I really dislike having to deal with programs that store the list of recently opened files or last window position in their settings file. (Looking at you KDE!)

And of course there are programs that put cache in the settings directory (chrome, anything electron...) which is also annoying since I don't want to include that in my file system snapshots (I use btrfs with subvolumes, so I can make different snapshotting policies for different directories, but it becomes a hassle when programs don't follow the XDG standard).

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.