Filesystem Access¶
In a nutshell
Sometimes a skill needs to save a file and read it back later: a high-score list, a downloaded image, a cache. OVOS gives every skill its own private folder for this, so skills cannot accidentally overwrite each other's files or scatter data across your computer. This page shows how to open, read, and write files in that folder. For saving small preference values instead, see Skill Settings. For term definitions, see the Glossary.
FileSystemAccess provides each skill with an isolated, XDG-compliant directory for persistent file storage. It prevents skills from accidentally writing to arbitrary locations and handles migration from legacy Mycroft paths.
Source: ovos_workshop/filesystem.py
Storage Path¶
Files for a skill are stored under:
<base_folder> defaults to mycroft (see Skill Settings for how it can be renamed system-wide). On most Linux installs this resolves to ~/.local/share/mycroft/filesystem/skills/<skill_id>/. The exact base is determined by get_xdg_data_save_path() from ovos-config. The directory is created automatically if it does not exist. (self.file_system is constructed as FileSystemAccess("skills/" + skill_id); a FileSystemAccess built directly with a bare name maps to filesystem/<name>/ without the skills/ segment.)
Migration from Legacy Paths¶
If a directory exists at the legacy dotfile location (~/.<base_folder>/skills/<skill_id>, e.g. ~/.mycroft/skills/<skill_id>) but the XDG path does not yet exist, the directory is automatically moved to the new location. A deprecation warning is logged during migration.
self.file_system Property¶
Every OVOSSkill (and OVOSAbstractApplication) exposes self.file_system, a FileSystemAccess instance pre-configured with the skill's skill_id. You do not need to construct FileSystemAccess manually.
# Inside a skill method:
with self.file_system.open("data.json", "w") as f:
import json
json.dump({"key": "value"}, f)
API¶
open(filename, mode)¶
Open a file inside the skill's sandboxed directory. Equivalent to open(skill_dir / filename, mode).
| Parameter | Description |
|---|---|
filename |
Filename relative to the skill's storage directory |
mode |
File open mode ("r", "w", "rb", "a", etc.) |
exists(filename)¶
Check whether a file exists inside the skill's sandboxed directory. Returns True or False.
path¶
self.file_system.path is the absolute path to the skill's storage directory. Use open() and exists() rather than accessing files directly by path when possible.
Persistent Files¶
def write_line_to_file(self, file_name, line):
"""Write a single line to a file in the skill's persistent filesystem."""
with self.file_system.open(file_name, "w") as my_file:
my_file.write(line)
def read_file(self, file_name):
"""Read the contents of a file in the skill's persistent filesystem."""
with self.file_system.open(file_name, "r") as my_file:
return my_file.read()
Check existence before reading:
file_name = "example.txt"
if self.file_system.exists(file_name):
content = self.read_file(file_name)
Subdirectories¶
file_system.open() does not create subdirectories automatically. Create them manually using os.mkdir:
from os import mkdir
from os.path import join
def initialize(self):
cache_dir = "cache"
if not self.file_system.exists(cache_dir):
mkdir(join(self.file_system.path, cache_dir))
with self.file_system.open(join(cache_dir, "example.txt"), "w") as f:
f.write("cached content")
Temporary Cache¶
For data that can be discarded and regenerated, use the get_cache_directory() helper from ovos-utils:
from os.path import join
from ovos_utils.file_utils import get_cache_directory
def initialize(self):
cache_dir = get_cache_directory("MySkill")
self.cache_file = join(cache_dir, "myfile.txt")
The cache directory may reside on a RAM disk and can be cleared at any time. Always handle the case where cached files are absent.
Skill Root Directory¶
The absolute path to the skill's installed root directory (e.g. ~/.local/share/mycroft/skills/my-skill.me/). Skills should not modify files inside root_dir. Doing so may trigger a skill reload.
Full Example¶
import json
from ovos_workshop.skills import OVOSSkill
from ovos_workshop.decorators import intent_handler
class HighScoreSkill(OVOSSkill):
"""Skill that persists a high score to disk."""
SCORES_FILE = "highscores.json"
def initialize(self):
self._scores = self._load_scores()
def _load_scores(self) -> dict:
if not self.file_system.exists(self.SCORES_FILE):
return {}
with self.file_system.open(self.SCORES_FILE, "r") as f:
return json.load(f)
def _save_scores(self):
with self.file_system.open(self.SCORES_FILE, "w") as f:
json.dump(self._scores, f, indent=2)
@intent_handler("get_high_score.intent")
def handle_get_score(self, message):
player = message.data.get("player", "anonymous")
score = self._scores.get(player, 0)
self.speak(f"{player} has a high score of {score}.")
@intent_handler("set_high_score.intent")
def handle_set_score(self, message):
player = message.data.get("player", "anonymous")
score = int(message.data.get("score", 0))
self._scores[player] = max(self._scores.get(player, 0), score)
self._save_scores()
self.speak(f"High score updated for {player}.")
Using FileSystemAccess Outside a Skill¶
from ovos_workshop.filesystem import FileSystemAccess
fs = FileSystemAccess("my-app.author")
# Files stored at $XDG_DATA_HOME/<base_folder>/filesystem/my-app.author/
# (e.g. ~/.local/share/mycroft/filesystem/my-app.author/ by default)
if not fs.exists("config.json"):
with fs.open("config.json", "w") as f:
import json
json.dump({"initialized": True}, f)
Source code: OpenVoiceOS/ovos-workshop.
Read next: Testing Your Skill Related: Resource Files · settingsmeta.json · Skill Settings · Locations