**_directories_
a tiny library that might help you** Rust Hack & Learn ⁄ 2018-03-19 ⁄ Karlsruhe # Problem Applications keep dumping files into `$HOME` ... ![](home.png) # Problem ... instead they should follow the standards of the operating system: - the [XDG base directory](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) and the [XDG user directory](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/) specifications on Linux - the [Known Folder](https://msdn.microsoft.com/en-us/library/windows/desktop/bb776911.aspx) API on Windows - the [Standard Directories](https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW6) guidelines on macOS # Motivation - Cleaner `$HOME` # Motivation - Cleaner `$HOME` - Delete caches with confidence # Motivation - Cleaner `$HOME` - Delete caches with confidence - Organize your data the way you want # Motivation - Cleaner `$HOME` - Delete caches with confidence - Organize your data the way you want - Tools like disk cleaners or backup apps do a better job # Motivation - Cleaner `$HOME` - Delete caches with confidence - Organize your data the way you want - Tools like disk cleaners or backup apps do a better job - Distribute and manage config files across multiple machines # Motivation - Cleaner `$HOME` - Delete caches with confidence - Organize your data the way you want - Tools like disk cleaners or backup apps do a better job - Distribute and manage config files across multiple machines - Well-behaved applications by default, not by manual configuration # Ideas 1. [Let's make `$HOME` read-only.](https://soc.github.io/articles/linux/self-defense-against-dotfiles.html) # Ideas 1. [Let's make `$HOME` read-only.](https://soc.github.io/articles/linux/self-defense-against-dotfiles.html) 2. [Let's fix _all_ the applications!](https://soc.github.io/articles/linux/xdg-are-we-there-yet.html) # Ideas 1. [Let's make `$HOME` read-only.](https://soc.github.io/articles/linux/self-defense-against-dotfiles.html) 2. [Let's fix _all_ the applications!](https://soc.github.io/articles/linux/xdg-are-we-there-yet.html) 3. Let's write a library that makes it easy for developers to get it right! # Example I Q: "Where is the config directory?" A: "It depends!" |Platform | Value | Example | | ------- | --------------------------- | -------------------------------- | | Linux | `$XDG_CONFIG_HOME` | /home/alice/.config | | macOS | `$HOME/Library/Preferences` | /Users/Alice/Library/Preferences | | Windows | `{FOLDERID_RoamingAppData}` | C:\Users\Alice\AppData\Roaming | # Example II Q: "Where is the download directory?" A: "It depends!" |Platform | Value | Example | | ------- | ---------------------- | ------------------------ | | Linux | `XDG_DOWNLOAD_DIR` | /home/alice/Downloads | | macOS | `$HOME/Downloads` | /Users/Alice/Downloads | | Windows | `{FOLDERID_Downloads}` | C:\Users\Alice\Downloads | # Example III Q: "Where does SuperApp – built by the UK company Mega Corp – store its cache files?" A: "It depends!" |Platform | Value | Example | | ------- | ---------------------------------------------- | ---------------------------------------------------- | | Linux | `$XDG_CACHE_HOME/_project_path_` | /home/alice/.cache/superapp | | macOS | `$HOME/Library/Caches/_project_path_` | /Users/Alice/Library/Caches/uk.co.Mega-Corp.SuperApp | | Windows | `{FOLDERID_LocalAppData}\_project_path_\cache` | C:\Users\Alice\AppData\Local\Mega Corp\SuperApp\cache | # Goals Design objectives of the _directories_ library: - Minimal conceptual footprint: a library, not a framework # Goals Design objectives of the _directories_ library: - Minimal conceptual footprint: a library, not a framework - Focus on directories the user (and therefore the app) controls # Goals Design objectives of the _directories_ library: - Minimal conceptual footprint: a library, not a framework - Focus on directories the user (and therefore the app) controls - Bridge the gap between operating systems without introducing traps # Goals Design objectives of the _directories_ library: - Minimal conceptual footprint: a library, not a framework - Focus on directories the user (and therefore the app) controls - Bridge the gap between operating systems without introducing traps - Make it easy to get things right # Implementation – JVM - First design implemented in Java (6) - Jar file size: 9kB - Final fields to access data - Returns `null` if directory not available - Supports Linux, macOS, Windows, BSD ## Implementation – JVM – API First version: ~~~~~~~ public abstract class BaseDirectories { public abstract class ProjectDirectories { protected String homeDir; protected String projectName; /* xdg base directories */ /* project directories */ protected String cacheDir; protected String projectCacheDir; protected String configDir; protected String projectConfigDir; protected String dataDir; protected String projectDataDir; protected String runtimeDir; /* factory methods */ /* xdg user directories */ public static ProjectDirectories protected String desktopDir; fromUnprocessedString(String value) { ... } protected String documentsDir; public static ProjectDirectories protected String downloadDir; fromQualifiedProjectName(String name) { ... } protected String musicDir; public static ProjectDirectories protected String picturesDir; fromProjectName(String name) { ... } protected String publicDir; } protected String templatesDir; protected String videosDir; // derived protected String executablesDir; protected String fontsDir; } ~~~~~~~ ## Implementation – JVM – Problems - Fields get computed even if they aren't used - Factory methods require knowledge of operating system conventions - Missing the difference between Window's `LocalAppData` and `RoamingAppData` ## Implementation – JVM – Problems: Windows Q: "How do we get the download directory on Windows?" A: "Well ... hold my beer." ## Implementation – JVM – Problems: Windows Q: "How do we get the download directory on Windows?" A: "Well ... hold my beer." ~~~~~~~ var knownFolderId = "Downloads"; new ProcessBuilder( "powershell.exe", "-Command", "[Environment]::GetFolderPath([Environment+SpecialFolder]::"+knownFolderId+")") ~~~~~~~ ## Implementation – JVM – Problems: Windows Q: "How do we get the download directory on Windows?" A: "Well ... hold my beer." ~~~~~~~ var knownFolderId = "Downloads"; new ProcessBuilder( "powershell.exe", "-Command", "[Environment]::GetFolderPath([Environment+SpecialFolder]::"+knownFolderId+")") ~~~~~~~ ************************************************************************************ * +-------+ * * +-->| Win32 | * * +---------------+ +---+---+-------+ * * | directories +---+ +-->| COM | * * +---------------+---v---+ +---+---+---------------+ * * | Java Standard Library +---+ +-->| .NET | * * +-----------------------+---v---+ +---+---+-----------------------+ * * | Java Virtual Machine +---+ +-->| PowerShell | * * +-------------------------------+---v------+---+-------------------------------+ * * | Operating system (Windows) | * * +------------------------------------------------------------------------------+ * ************************************************************************************ # Implementation – Rust - Existing library situation: not so great | Library | Status | Lin | Mac | Win |Base|User|Proj|Conv| | ------------- | ------------- |:---:|:---:|:---:|:--:|:--:|:--:|:--:| | `dirs` | Unmaintained? | ✔ | ✔ | ✔ | 🞈 | ✖ | ✔ | ✖ | | `s_app_dir` | Unmaintained? | ✔ | ✖ | 🞈 | ✖ | ✖ | 🞈 | ✖ | | `xdg` | Maintaind | ✔ | ✖ | ✖ | ✔ | ✖ | ✔ | 🞈 | | `xdg-basedir` | Unmaintained? | ✔ | ✖ | ✖ | ✔ | ✖ | ✖ | 🞈 | | `xdg-rs` | Obsolete | ✔ | ✖ | ✖ | ✔ | ✖ | ✖ | 🞈 | ## Implementation – Rust – API ~~~~~~ #[derive(Debug, Clone)] #[derive(Debug, Clone)] pub struct BaseDirs { pub struct UserDirs { /* home directory */ /* home directory */ home_dir: PathBuf, home_dir: PathBuf, /* base directories */ /* user directories */ cache_dir: PathBuf, audio_dir: Option<PathBuf>, config_dir: PathBuf, desktop_dir: Option<PathBuf>, data_dir: PathBuf, document_dir: Option<PathBuf>, data_local_dir: PathBuf, download_dir: Option<PathBuf>, executable_dir: Option<PathBuf>, font_dir: Option<PathBuf>, runtime_dir: Option<PathBuf> picture_dir: Option<PathBuf>, } public_dir: Option<PathBuf>, template_dir: Option<PathBuf>, #[derive(Debug, Clone)] // trash_dir: PathBuf, pub struct ProjectDirs { video_dir: Option<PathBuf> project_path: PathBuf, } /* base directories */ cache_dir: PathBuf, config_dir: PathBuf, data_dir: PathBuf, data_local_dir: PathBuf, runtime_dir: Option<PathBuf> } ~~~~~~ ## Implementation – Rust – Changes - Split `BaseDirs` into `BaseDirs` and `UserDirs` - Added distinction between local and roaming data ************************************************************************************ * +------------------+ +------------------+ * * +-->| data_dir +--->| data_local_dir +---> * * / +------------------+ +------------------+ * * +----------+ * * | data_dir | v0.0.1 v0.4.0 * * +----------+ * * \ +------------------+ +------------------+ * * +-->| data_roaming_dir +--->| data_dir +---> * * +------------------+ +------------------+ * ************************************************************************************ ## Implementation – Rust – Changes - Split `BaseDirs` into `BaseDirs` and `UserDirs` - Added distinction between local and roaming data - Overhaul factory functions ~~~~~~ impl BaseDirs { pub fn new() -> BaseDirs { ... } } impl UserDirs { pub fn new() -> UserDirs { ... } } impl ProjectDirs { pub fn from(qualifier: &str, organization: &str, project: &str) -> ProjectDirs { ... } // use strongly discouraged pub fn from_path(project_path: PathBuf) -> ProjectDirs { ... } } ~~~~~~ ## Implementation – Rust – Changes - Split `BaseDirs` into `BaseDirs` and `UserDirs` - Added distinction between local and roaming data - Overhaul factory functions ************************************************************************************ * +-----------------------------------------------------+ * * | ProjectDirs::from("uk.co", "Mega Corp", "SuperApp") | * * +----+---------------------+---------------------+----+ * * / | \ * * Linux / macOS | Windows \ * * / | \ * * v v v * * superapp/ uk.co.Mega-Corp.SuperApp/ Mega Corp\SuperApp\ * ************************************************************************************ ## Implementation – Rust – Changes - Added distinction between local and roaming data - Split `BaseDirs` into `BaseDirs` and `UserDirs` - Overhaul factory functions - Abbreviations: - Rename `Directories` to `Dirs` - Remove `project` prefix from `ProjectDirs` functions ## Implementation – Rust – Comparison | Library | Status | Lin | Mac | Win |Base|User|Proj|Conv| | ----------------- | -------------- |:---:|:---:|:---:|:--:|:--:|:--:|:--:| | `dirs` | Unmaintained? | ✔ | ✔ | ✔ | 🞈 | ✖ | ✔ | ✖ | | `s_app_dir` | Unmaintained? | ✔ | ✖ | 🞈 | ✖ | ✖ | 🞈 | ✖ | | `xdg` | Maintaind | ✔ | ✖ | ✖ | ✔ | ✖ | ✔ | 🞈 | | `xdg-basedir` | Unmaintained? | ✔ | ✖ | ✖ | ✔ | ✖ | ✖ | 🞈 | | `xdg-rs` | Obsolete | ✔ | ✖ | ✖ | ✔ | ✖ | ✖ | 🞈 | | `directories-jvm` | "Developed" | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | `directories-rs` | "Developed" | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | # Overview | Library | Version | Artifacts | Docs | Sources | Examples | | ----------------- | -------:| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------- | | `directories-jvm` | 7 | [Maven Central](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22io.github.soc%22%20AND%20a%3A%22directories%22) | [javadoc.io](http://javadoc.io/doc/io.github.soc/directories) | [GitHub](https://github.com/soc/directories-jvm) | [coursier](https://github.com/coursier/coursier/pull/676) | | `directories-rs` | 0.8.4 | [crates.io](https://crates.io/crates/directories) | [docs.rs](https://docs.rs/directories/0.8.3/directories/) | [GitHub](https://github.com/soc/directories-rs) | [cargo](https://github.com/rust-lang/cargo/pull/5183) | # Questions? # Thanks!