Repository: SasView/sasview
Pull request #3166: Use platformdirs package for all user-related file paths
Author: login: krzywon
Labels: name: Discuss At The Call
description: Issues to be discussed at the fortnightly call
Created: 2025-01-03T21:26:00Z | Merged: 2025-04-09T16:52:54Z

## Description

This moves our user file handling to the `platformdirs` package for os preferred pathing. This is used to store log files, configurations, and app data. This was suggested by @llimeht in the wheels discussion in #3161. This automatically creates subfolders for each version so docs no longer require versioning in their paths.

### What has changed:
- No longer storing data in `~/.sasview`
  - The files are not all be in the same place depending on the OS
- Using more OS prefered storage locations for application files
- Updated calls to the directories to ensure new locations are used for docs, plugin models, the log file, categories, and config files.
- Added user documentation to let users know where these files live (Waiting on Flatpak info...)

### Good ideas, but outside the scope of this PR
- Add a method to open explorer windows directly to each file directory for to give the user easy access to the directories - OS-agnostic methods to do this don't exist.
- Copy example data from the as-installed location to the user directory - Working on in separate branch built off this one: https://github.com/SasView/sasview/tree/platfromdirs-example-data-move

### Other work to be done:
- Any site on sasview.org that references the user directory will need to be updated - https://github.com/SasView/sasview.github.io/issues/28

Fixes #3142

## How Has This Been Tested?

CI is able to launch both the Windows and Ubuntu 22 binaries.

## Review Checklist:

**Documentation** (check at least one)
- [ ] There is **nothing** that needs documenting
- [x] Documentation changes are **in this PR**
- [ ] There is an **issue** open for the documentation

**Installers**
- [X] This **will** affect the **installers**
  - [ ] **Windows** installer (GH artifact) has been tested (installed and worked) 
  - [ ] **MacOSX** installer (GH artifact) has been tested (installed and worked) 

**Licencing** (untick if necessary)
- [x] The introduced changes comply with SasView license (BSD 3-Clause)



Linked issue #3142: Linux filepaths do not comply with the XDG Base Directory Specification
**Describe the bug**
This was raised during the review of the Flatpak. Right now, we are storing all of the configuration files, and compiled models that SasView generates in a directory that lives directly on the home directory. This does not comply with the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/latest/)  (a kind of standard for Linux desktop applications).

Configuration files should be placed under `~/.config/sasview`, and user specific data should be stored in `~/.local/share/sasview`. The latter should also included the `sasview.log` file which should not be on the home directory. The idea of this is to not pollute the user's home directory.

This is a low priority issue as it should hopefully not block the Flatpak from being published. In fact, many popular applications break this rule as well. But it is probably a change we'll want to make in the future to promote better practices.

If a fix were to be implemented, there is an issue of older installations using the old directories, and then these being missed by newer installations. To get around this, I would suggest having a precedence system where we check one location first before checking another directory. This is a common approach taken by many other Linux applications.

**SasView version:**
 - Version: 6.0.0

**Operating system:**
 - OS: Any Linux distribution

Diff:
diff --git a/build_tools/requirements.txt b/build_tools/requirements.txt
index a6bc0a64cb..4b1a3989f0 100644
--- a/build_tools/requirements.txt
+++ b/build_tools/requirements.txt
@@ -16,6 +16,7 @@ matplotlib
 numba
 numpy
 periodictable
+platformdirs
 pybind11
 pylint
 pyopengl
diff --git a/docs/sphinx-docs/build_sphinx.py b/docs/sphinx-docs/build_sphinx.py
index 91f8010100..1859d72e49 100755
--- a/docs/sphinx-docs/build_sphinx.py
+++ b/docs/sphinx-docs/build_sphinx.py
@@ -19,8 +19,6 @@
 from distutils.util import get_platform
 from distutils.spawn import find_executable
 
-from sas.system.user import get_user_dir
-
 platform = '.%s-%s'%(get_platform(),sys.version[:3])
 
 # sphinx paths
diff --git a/docs/sphinx-docs/source/user/working.rst b/docs/sphinx-docs/source/user/working.rst
index b472cdbf70..5ca192d3fc 100644
--- a/docs/sphinx-docs/source/user/working.rst
+++ b/docs/sphinx-docs/source/user/working.rst
@@ -25,3 +25,5 @@ Working with SasView
    Model Marketplace <marketplace>
 
    Preferences <qtgui/MainWindow/preferences_help.rst>
+
+   User Files <qtgui/MainWindow/sasview_files>
diff --git a/installers/installer.iss b/installers/installer.iss
index 9506b44440..b140ed59c8 100644
--- a/installers/installer.iss
+++ b/installers/installer.iss
@@ -61,7 +61,7 @@ end;
 
 [Files]
 Source: "dist\sasview\sasview.exe"; DestDir: "{app}"; Flags: ignoreversion
-Source: "dist\sasview\plugin_models\*"; DestDir: "{%USERPROFILE}\.sasview\plugin_models"
+Source: "dist\sasview\plugin_models\*"; DestDir: "{%USERPROFILE}\AppData\Local\sasview\SasView\plugin_models"
 Source: "dist\sasview\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
 
 [InstallDelete]
diff --git a/installers/sasview.spec b/installers/sasview.spec
index 2e5933101c..d4b8bda426 100644
--- a/installers/sasview.spec
+++ b/installers/sasview.spec
@@ -19,7 +19,6 @@ datas = [
     ('../src/sas/qtgui/Utilities/WhatsNew/messages', 'sas/qtgui/Utilities/WhatsNew/messages'),
     ('../src/sas/qtgui/Utilities/WhatsNew/css/style.css', 'sas/qtgui/Utilities/WhatsNew/css'),
     ('../src/sas/qtgui/Utilities/About/images', 'sas/qtgui/Utilities/About/images'),
-    ('../src/sas/system/log.ini', 'sas/system/'),
     ('../../sasmodels/sasmodels','sasmodels'),
     ('../docs/sphinx-docs/build','doc/build'),
     ('../docs/sphinx-docs/source-temp','doc/source')
diff --git a/src/sas/cli.py b/src/sas/cli.py
index ead3a822d9..5cb3e00261 100644
--- a/src/sas/cli.py
+++ b/src/sas/cli.py
@@ -104,6 +104,10 @@ def parse_cli(argv):
     return opts
 
 def main(logging="production"):
+    # Copy files before loading config
+    from sas.system.user import copy_old_files_to_new_location
+    copy_old_files_to_new_location()
+
     from sas.system import log
     from sas.system import lib
     from sas.system import console
diff --git a/src/sas/qtgui/MainWindow/media/sasview_files.rst b/src/sas/qtgui/MainWindow/media/sasview_files.rst
new file mode 100644
index 0000000000..430f05f8b0
--- /dev/null
+++ b/src/sas/qtgui/MainWindow/media/sasview_files.rst
@@ -0,0 +1,108 @@
+.. sasview_files.rst
+
+.. Initial Draft: J Krzywon, Apr 2025
+.. Last Updated: J Krzywon, Apr. 9, 2025
+
+.. _UserFiles:
+
+SasView User Files
+==================
+
+During the installation process and while running the app, SasView creates a number of files that are stored outside
+the install location on the end users computer. These files include configurations, plugin models, compiled plugins,
+documentation, and example files. Please refer to the specific section to find where each type of file is stored.
+
+File paths that start with the '~' character refer to the user directory, which is typically `C:\\\\Users\\<username>` in
+Windows, `/Users/<username>` in MacOS, and `/home/<username>` in most Linux distributions.
+
+This document is specific to v6.1.0 and beyond. Previous versions of SasView stored user files in the
+`~/.sasview` directory.
+
+**For users who wish to use versions both newer and older than v6.1, do not delete the contents
+of the `~/.sasview` directory.**
+
+
+:ref:`Config_Files`, :ref:`Log_Files`, :ref:`Plugin_Files`, :ref:`Compiled_Files`, :ref:`Documentation`
+
+.. , :ref:`Example_Data`
+
+.. _Config_Files:
+
+Configuration Files
+--------------------
+SasView stores two types of config file on the user disk, a general configuration file for each major version of sasview,
+and a categories file that allows the end user to change how their models are organized in the fitting perspective. The
+general configuration file, config-<v>.json is a json file that stores a mapping of config variables to their updated values.
+This file is only generated and stored locally if something in the :ref:`Preferences` panel has been modified. The categories
+file, categories.json, stores the user-preferred model organization, allowing a user to add model categories, move models
+into new categories, and not show certain models. More information on this feature is available in :ref:`Category_Manager`.
+
+OS-specific file locations for configuration files:
+ - Windows: ~/AppData/Local/sasview/SasView/
+ - MacOS: ~/Library/Application Support/SasView/
+ - Linux: ~/.config/SasView/
+
+.. _Log_Files:
+
+Log Files
+---------
+SasView creates a time-stamped log file that includes all messages entered into the log explorer, as well as a number of
+other messages related to application startup and shutdown.
+
+OS-specific file locations for log files:
+ - Windows: ~/AppData/Local/sasview/SasView/Logs/
+ - MacOS: ~/Library/Logs/SasView/
+ - Linux: ~/.local/state/SasView/
+
+.. _Plugin_Files:
+
+Plugin Files
+------------
+Plugin models, whether they be written by the user (:ref:`Writing_a_Plugin`), downloaded from the
+`model marketplace <https://marketplace.sasview.org/>`_, or sent from another SasView user, should be stored in a
+specific location to allow SasView to find them.
+
+OS-specific file locations for plugin files:
+ - Windows: ~/AppData/Local/sasview/SasView/plugin_models/
+ - MacOS: ~/Library/Application Support/SasView/plugin_models/
+ - Linux: ~/.local/share/SasView/plugin_models
+
+.. _Compiled_Files:
+
+Complied Model Files
+------------
+The fitting module, when a model is selected, compiles each model in an on-demand process. The compiled file will include
+all form factors and structure factors used to create the model, and will be compiled based on the GPU optimization
+currently in use. These models are stored in a parallel directory to the plugin models, but end users do not need to add
+any files to this directory.
+
+OS-specific file locations for compiled model files:
+ - Windows: ~/AppData/Local/sasview/SasView/compiled_models/
+ - MacOS: ~/Library/Application Support/SasView/compiled_models/
+ - Linux: ~/.local/share/SasView/compiled_models
+
+.. _Documentation:
+
+Documentation
+-------------
+SasView moves its documentation into a user location when launching the app, if the documentation does not already exist.
+Each version of SasView has its own documentation that may be different between versions, so a separate directory is used
+for each version of the doc files.
+
+OS-specific file locations for documentation:
+ - Windows: ~/AppData/Local/sasview/SasView/<sasview.version>/doc/
+ - MacOS: ~/Library/Application Support/SasView/<sasview.version>/doc/
+ - Linux: ~/.local/share/SasView/<sasview.version>/doc/
+
+..
+.. _Example_Data:
+
+.. Example Data
+.. ------------
+.. SasView supplies a number of example data files that may be used to orient yourself with the application. More information
+.. on the included files is available at :ref:`example_data_help`. These files are moved to the user directory on install.
+
+.. OS-specific file locations for example data:
+.. - Windows: ~/AppData/Local/sasview/SasView/example_data/
+.. - MacOS: ~/Library/Application Support/SasView/example_data/
+.. - Linux: <TODO>
diff --git a/src/sas/qtgui/Utilities/CategoryInstaller.py b/src/sas/qtgui/Utilities/CategoryInstaller.py
index a50d557764..8cdbbecfb6 100644
--- a/src/sas/qtgui/Utilities/CategoryInstaller.py
+++ b/src/sas/qtgui/Utilities/CategoryInstaller.py
@@ -14,6 +14,8 @@
 import logging
 from collections import defaultdict, OrderedDict
 
+from sas.system.user import get_config_dir
+
 USER_FILE = 'categories.json'
 
 class CategoryInstaller:
@@ -31,7 +33,7 @@ def _get_home_dir():
         """
         returns the users sasview config dir
         """
-        return os.path.join(os.path.expanduser("~"), ".sasview")
+        return get_config_dir()
 
     @staticmethod
     def _regenerate_model_dict(master_category_dict):
diff --git a/src/sas/sascalc/doc_regen/makedocumentation.py b/src/sas/sascalc/doc_regen/makedocumentation.py
index a40690be4f..920eec4170 100644
--- a/src/sas/sascalc/doc_regen/makedocumentation.py
+++ b/src/sas/sascalc/doc_regen/makedocumentation.py
@@ -12,17 +12,16 @@
 from typing import Union
 
 from sas.sascalc.fit import models
-from sas.system.version import __version__
-from sas.system.user import get_user_dir
+from sas.system.user import get_app_dir_versioned
 
 from sasmodels.core import list_models
 
 PATH_LIKE = Union[Path, str, os.PathLike]
 
 # Path constants related to the directories and files used in documentation regeneration processes
-USER_DIRECTORY = Path(get_user_dir())
-USER_DOC_BASE = USER_DIRECTORY / "doc"
-USER_DOC_SRC = USER_DOC_BASE / str(__version__)
+APP_DIRECTORY = Path(get_app_dir_versioned())
+USER_DOC_BASE = APP_DIRECTORY / "doc"
+USER_DOC_SRC = USER_DOC_BASE
 USER_DOC_LOG = USER_DOC_SRC / 'log'
 DOC_LOG = USER_DOC_LOG / 'output.log'
 MAIN_DOC_SRC = USER_DOC_SRC / "source-temp"
diff --git a/src/sas/sascalc/doc_regen/regenmodel.py b/src/sas/sascalc/doc_regen/regenmodel.py
index 3db4e70535..4c85e15f3a 100644
--- a/src/sas/sascalc/doc_regen/regenmodel.py
+++ b/src/sas/sascalc/doc_regen/regenmodel.py
@@ -413,7 +413,7 @@ def main():
         help="model files ")
     args = parser.parse_args()
 
-    TARGET_DIR = os.path.expanduser(args.rst)
+    TARGET_DIR = Path(TARGET_DIR / args.rst) if args.rst else TARGET_DIR
     if not os.path.exists(TARGET_DIR) and not args.sphinx:
         print("build directory %r does not exist"%TARGET_DIR)
         sys.exit(1)
diff --git a/src/sas/sascalc/fit/models.py b/src/sas/sascalc/fit/models.py
index a03edbcbde..595aa5554a 100644
--- a/src/sas/sascalc/fit/models.py
+++ b/src/sas/sascalc/fit/models.py
@@ -1,8 +1,6 @@
 """
     Utilities to manage models
 """
-from __future__ import print_function
-
 import os
 import sys
 import time
@@ -15,7 +13,7 @@
 
 from sasmodels.sasview_model import load_custom_model, load_standard_models
 
-from sas.system.user import get_user_dir
+from sas.system.user import get_plugin_dir
 
 # Explicitly import from the pluginmodel module so that py2exe
 # places it in the distribution. The Model1DPlugin class is used
@@ -26,7 +24,7 @@
 
 
 PLUGIN_DIR = 'plugin_models'
-PLUGIN_LOG = os.path.join(get_user_dir(), PLUGIN_DIR, "plugins.log")
+PLUGIN_LOG = os.path.join(get_plugin_dir(), "plugins.log")
 PLUGIN_NAME_BASE = '[plug-in] '
 
 
@@ -82,29 +80,14 @@ def _check_plugin(model, name):
     return model
 
 
-def find_plugins_dir():
-    """
-    Find path of the plugins directory.
-    The plugin directory is located in the user's home directory.
+def find_plugins_dir() -> str:
+    """A helper function that returns a string representation of the plugins directory as defined by sas.system.user.
     """
-    path = os.path.join(os.path.expanduser("~"), '.sasview', PLUGIN_DIR)
-
-    # TODO: trigger initialization of plugins dir from installer or startup
-    # If the plugin directory doesn't exist, create it
-    if not os.path.isdir(path):
-        os.makedirs(path)
-    # TODO: should we be checking for new default models every time?
-    # TODO: restore support for default plugins
-    #initialize_plugins_dir(path)
-    return path
+    return str(get_plugin_dir())
 
 
 def initialize_plugins_dir(path):
     # TODO: There are no default plugins
-    # TODO: Default plugins directory is in sasgui, but models.py is in sascalc
-    # TODO: Move default plugins beside sample data files
-    # TODO: Should not look for defaults above the root of the sasview install
-
     # Walk up the tree looking for default plugin_models directory
     base = os.path.abspath(os.path.dirname(__file__))
     for _ in range(12):
diff --git a/src/sas/system/config/config_meta.py b/src/sas/system/config/config_meta.py
index c5fb60d444..3555156137 100644
--- a/src/sas/system/config/config_meta.py
+++ b/src/sas/system/config/config_meta.py
@@ -58,7 +58,7 @@ def defaults(self):
     def config_filename(self, create_if_nonexistent=False):
         """Filename for saving config items"""
         version_parts = sas.system.version.__version__.split(".")
-        user_dir = user.get_user_dir(create_if_nonexistent)
+        user_dir = user.get_config_dir(create_if_nonexistent)
         return os.path.join(user_dir, f"config-{version_parts[0]}.json")
 
     def finalise(self):
diff --git a/src/sas/system/lib.py b/src/sas/system/lib.py
index 203da28470..596ad9dd2f 100644
--- a/src/sas/system/lib.py
+++ b/src/sas/system/lib.py
@@ -8,7 +8,7 @@
 # TODO: Add api to control sasmodels rather than using environment variables
 def setup_sasmodels():
     """Initialize sasmodels settings from the sasview configuration."""
-    from .user import get_user_dir
+    from .user import get_app_dir
 
     # Don't need to set SAS_MODELPATH for gui because sascalc.fit uses the
     # full paths to models, but when using the sasview package as a python
@@ -21,7 +21,7 @@ def setup_sasmodels():
     # Both scripts and gui need to know the stored DLL path.
     if "SAS_DLL_PATH" not in os.environ:
         os.environ["SAS_DLL_PATH"] = os.path.join(
-            get_user_dir(), "compiled_models")
+            get_app_dir(), "compiled_models")
 
     # Set OpenCL config from environment variable if it is set otherwise
     # use the value from the sas config file.
diff --git a/src/sas/system/log.ini b/src/sas/system/log.ini
deleted file mode 100644
index d9c6d4d473..0000000000
--- a/src/sas/system/log.ini
+++ /dev/null
@@ -1,45 +0,0 @@
-
-###############################################################################
-################################### LOGGING ###################################
-###############################################################################
-# Main logger for SASView
-
-# SEE: https://docs.python.org/2/library/logging.html#logrecord-attributes
-[formatters]
-keys=simple,detailed
-
-[formatter_simple]
-format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
-#format=%(asctime)s - %(levelname)s : %(name)s:%(pathname)s:%(lineno)4d: %(message)s
-#format=%(asctime)s - %(levelname)s : %(name)s:%(lineno)4d: %(message)s
-#format=%(asctime)s - %(levelname)s: %(message)s
-datefmt=%H:%M:%S
-
-[formatter_detailed]
-#format=%(asctime)s : %(levelname)s : %(name)s: %(lineno)d: %(message)s
-format=%(asctime)s : %(levelname)s : %(name)s (%(filename)s:%(lineno)s) :: %(message)s
-
-###############################################################################
-# Handlers
-
-[handlers]
-keys=console,log_file
-
-[handler_console]
-class=logging.StreamHandler
-formatter=simple
-
-[handler_log_file]
-class=logging.FileHandler
-formatter=detailed
-args=(os.path.join(os.path.expanduser("~"),'sasview.log'),"a")
-
-
-###############################################################################
-# Loggers
-
-[loggers]
-keys=root
-
-[logger_root]
-handlers=console,log_file
diff --git a/src/sas/system/log.py b/src/sas/system/log.py
index 45231e8a57..e7342cb123 100644
--- a/src/sas/system/log.py
+++ b/src/sas/system/log.py
@@ -6,7 +6,7 @@
 import sys
 import os.path
 
-import importlib.resources
+from sas.system.user import get_log_dir
 
 '''
 Module that manages the global logging
@@ -42,12 +42,16 @@ def setup_logging(level=logging.INFO):
 
     # Apply the logging config after setting the defaults
     try:
-        fd = importlib.resources.open_text('sas.system', 'log.ini')
-        logging.config.fileConfig(fd)
-    except FileNotFoundError:
-        print(f"ERROR: Log config '{fd.name}' not found...", file=sys.stderr)
-
-    #print_config()
+        filename = os.path.join(get_log_dir(), 'sasview.log')
+        fh = logging.FileHandler(filename=filename, mode="a")
+    except IOError:
+        print(f"ERROR: Log config '{filename}' not found...", file=sys.stderr)
+        fh = None
+    ch = logging.StreamHandler()
+    handlers = [fh, ch] if fh else [ch]
+    logging.basicConfig(format='%(asctime)s : %(levelname)s : %(name)s (%(filename)s:%(lineno)s) :: %(message)s',
+                        handlers=handlers)
+    # print_config()
 
 def production():
     setup_logging('INFO')
diff --git a/src/sas/system/user.py b/src/sas/system/user.py
index 86df6c3a54..b39607713d 100644
--- a/src/sas/system/user.py
+++ b/src/sas/system/user.py
@@ -1,18 +1,122 @@
 import os
+import shutil
+from pathlib import Path
+from platformdirs import PlatformDirs
+from sas.system.version import __version__ as sasview_version
 
-_USER_DIR = os.path.join(os.path.expanduser("~"), '.sasview')
+# Create separate versioned and unversioned file locations
+PLATFORM_DIRS_VERSIONED = PlatformDirs("SasView", "sasview", version=sasview_version)
+PLATFORM_DIRS_UNVERSIONED = PlatformDirs("SasView", "sasview")
+# Deprecated path
+_USER_DIR = Path(os.path.join(os.path.expanduser("~")))
+# OS agnostic pathing
+_APP_DATA_DIR = PLATFORM_DIRS_UNVERSIONED.user_data_dir
+_APP_VERS_DIR = PLATFORM_DIRS_VERSIONED.user_data_dir
+_CACHE_DIR = PLATFORM_DIRS_VERSIONED.user_cache_dir
+_CONFIG_DIR = PLATFORM_DIRS_UNVERSIONED.user_config_dir
+_LOG_DIR = PLATFORM_DIRS_UNVERSIONED.user_log_dir
 
+PATH_TYPE = [Path, os.path, str]
 
-def get_user_dir(create_if_nonexistent=True):
-    """
-    The directory where the per-user configuration is stored.
 
-    Returns ~/.sasview, creating it if it does not already exist.
+def get_dir_and_create_if_needed(path: PATH_TYPE, create_if_nonexistent: bool = True) -> Path:
+    """Returns the requested directory as a pathlib.Path object, creating the directory if it does not already exist."""
+    path = Path(path)
+    if create_if_nonexistent and not os.path.exists(path):
+        path.mkdir(parents=True, exist_ok=True)
+    return path
+
+
+def _get_user_dir(create_if_nonexistent: bool = False) -> Path:
+    """**DEPRECATED** Do not use this function to create new files.
+
+    v6.1.0: This is only used to move files from previous version locations to new locations
     """
     global _USER_DIR
+    return get_dir_and_create_if_needed(_USER_DIR, create_if_nonexistent)
+
+
+def get_config_dir(create_if_nonexistent: bool = True) -> Path:
+    """The directory where os-specific configurations are stored.
+
+    Returns the directory as a pathlib.Path object, creating the directory if it does not already exist.
+    """
+    global _CONFIG_DIR
+    return get_dir_and_create_if_needed(_CONFIG_DIR, create_if_nonexistent)
+
+
+def get_app_dir(create_if_nonexistent: bool = True) -> Path:
+    """The directory where the os-specific app data is stored.
+
+    Returns the directory as a pathlib.Path object, creating the directory if it does not already exist.
+    """
+    global _APP_DATA_DIR
+    return get_dir_and_create_if_needed(_APP_DATA_DIR, create_if_nonexistent)
+
+
+def get_app_dir_versioned(create_if_nonexistent: bool = True) -> Path:
+    """The directory where the version-dependent, os-specific app data is stored.
+
+    Returns the directory as a pathlib.Path object, creating the directory if it does not already exist.
+    """
+    global _APP_VERS_DIR
+    return get_dir_and_create_if_needed(_APP_VERS_DIR, create_if_nonexistent)
+
+
+def get_log_dir(create_if_nonexistent: bool = True) -> Path:
+    """The directory where the os-specific logs are stored.
+
+    Returns the directory as a pathlib.Path object, creating the directory if it does not already exist.
+    """
+    global _LOG_DIR
+    return get_dir_and_create_if_needed(_LOG_DIR, create_if_nonexistent)
+
+
+def get_cache_dir(create_if_nonexistent: bool = True) -> Path:
+    """The directory where the os-specific cache is stored.
+
+    Returns the directory as a pathlib.Path object, creating the directory if it does not already exist.
+    """
+    global _CACHE_DIR
+    return get_dir_and_create_if_needed(_CACHE_DIR, create_if_nonexistent)
+
+
+def get_plugin_dir(create_if_nonexistent: bool = True) -> Path:
+    """The directory where the os-specific cache for plugin models is stored.
+
+    Returns the directory as a pathlib.Path object, creating the directory if it does not already exist.
+    """
+    app_dir = get_app_dir(create_if_nonexistent)
+    return get_dir_and_create_if_needed(Path(app_dir, 'plugin_models'), create_if_nonexistent)
+
+
+def copy_old_files_to_new_location():
+    """**Only run at app startup**
+    A check to see if files in the old user locations exist and move them if they haven't already been moved."""
+
+    # Get the user directory location and original .sasview directory
+    user_dir = Path(_get_user_dir())
+    old_sasview_usr_dir = user_dir / '.sasview'
+
+    # Values used multiple times that need to be defined
+    config_name = f'config-{sasview_version.split(".")[0]}'
+    old_plugins = old_sasview_usr_dir / 'plugin_models'
+    if old_plugins.exists():
+        plugin_files = [f for f in os.listdir(old_plugins) if os.path.isfile(os.path.join(old_plugins, f))]
+    else:
+        plugin_files = []
 
-    if create_if_nonexistent and not os.path.exists(_USER_DIR):
-            os.mkdir(_USER_DIR)
+    # Create a dictionary mapping old file locations to their respective new locations and populate it with
+    #  the log and config file locations.
+    location_map = {
+        user_dir / 'sasview.log': Path(get_log_dir()) / 'sasview.log',  # Log file
+        old_sasview_usr_dir / config_name: Path(get_config_dir()) / config_name  # Configuration file
+    }
+    # Add any existing plugin files
+    location_map.update({old_plugins / f_name: Path(get_plugin_dir()) / f_name for f_name in plugin_files})
 
-    return _USER_DIR
+    # Iterate through dictionary, check if new files exist, and move files that haven't already been created
+    for old_path, new_path in location_map.items():
+        if old_path.exists() and not new_path.exists():
+            shutil.copy2(old_path, new_path)
 


Reviews:
user: login: pkienzle
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-20T19:47:22Z
user: login: jamescrake-merani
body: It looks like I was wrong at the fortnightly meeting. The problem isn't a GitHub action permission error like I expected as I'm also able to reproduce it on my machine.

I have left a comment on the line I think is the culprit.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T11:47:11Z
user: login: krzywon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T12:41:59Z
user: login: krzywon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T13:24:33Z
user: login: jamescrake-merani
body: It now works on my machine, and code LGTM.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T09:17:43Z
user: login: codescene-delta-analysis[bot]
body: 
**Gates Failed**
[![](https://codescene.io/imgs/svg/x1.svg)](#) Enforce advisory code health rules `(1 file with Complex Method)`

**Gates Passed**
[![](https://codescene.io/imgs/svg/pass1.svg)](#) 3 Quality Gates Passed


[See analysis details in CodeScene](https://codescene.io/projects/64300/delta/results/3702648)
<details><summary>Reason for failure</summary>

| Enforce advisory code health rules | Violations | Code Health Impact |         |
|-|----|-|--|
| user.py | [1 advisory rule](https://github.com/SasView/sasview/pull/3166#discussion_r2029431433) | 10.00 → 9.69 | [Suppress](https://codescene.io/projects/64300/delta?repo-id=263464&review-id=3166&biomarker=Complex+Method&filename=src%2Fsas%2Fsystem%2Fuser.py&method=copy_old_files_to_new_location&suppress=true) |

</details>



##              
**Quality Gate Profile:** [Clean Code Collective](https://codescene.io/projects/64300/config/delta-analysis)
Want more control? [Customize Code Health rules](https://codescene.io/docs/guides/technical/code-health.html#adapt-code-health-to-your-coding-standards) or catch issues early with our [IDE extension](https://codescene.com/developer-hub) and [CLI tool](https://codescene.io/docs/cli/index.html).
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-04T21:03:24Z
user: login: codescene-delta-analysis[bot]
state: APPROVED
author_association: NONE
submitted_at: 2025-04-07T15:47:00Z
user: login: codescene-delta-analysis[bot]
state: APPROVED
author_association: NONE
submitted_at: 2025-04-07T18:48:50Z
user: login: codescene-delta-analysis[bot]
state: APPROVED
author_association: NONE
submitted_at: 2025-04-08T14:20:38Z
user: login: jamescrake-merani
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T15:04:45Z
user: login: jamescrake-merani
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T15:05:35Z
user: login: jamescrake-merani
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T11:02:51Z
user: login: jamescrake-merani
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T11:03:47Z
user: login: codescene-delta-analysis[bot]
state: APPROVED
author_association: NONE
submitted_at: 2025-04-09T12:02:41Z
user: login: jamescrake-merani
body: Code LGTM. Just a few comments regarding the documentation.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T13:53:31Z
user: login: codescene-delta-analysis[bot]
state: APPROVED
author_association: NONE
submitted_at: 2025-04-09T15:11:15Z
user: login: krzywon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T15:17:21Z
user: login: jamescrake-merani
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T16:34:50Z
user: login: jamescrake-merani
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T16:35:18Z

Review comments:
diff_hunk: @@ -1,18 +1,120 @@
 import os
+import shutil
+from pathlib import Path
+from platformdirs import PlatformDirs
+from sas.system.version import __version__ as sasview_version
 
-_USER_DIR = os.path.join(os.path.expanduser("~"), '.sasview')
+# Create separate versioned and unversioned file locations
+PLATFORM_DIRS_VERSIONED = PlatformDirs("SasView", "sasview", version=sasview_version)
+PLATFORM_DIRS_UNVERSIONED = PlatformDirs("SasView", "sasview")
+# Deprecated path
+_USER_DIR = Path(os.path.join(os.path.expanduser("~")))
+# OS agnostic pathing
+_APP_DATA_DIR = PLATFORM_DIRS_UNVERSIONED.user_data_dir
+_APP_VERS_DIR = PLATFORM_DIRS_VERSIONED.user_data_dir
+_CACHE_DIR = PLATFORM_DIRS_VERSIONED.user_cache_dir
+_CONFIG_DIR = PLATFORM_DIRS_UNVERSIONED.user_config_dir
+_LOG_DIR = PLATFORM_DIRS_UNVERSIONED.user_log_dir
 
+PATH_TYPE = [Path, os.path, str]
 
-def get_user_dir(create_if_nonexistent=True):
-    """
-    The directory where the per-user configuration is stored.
 
-    Returns ~/.sasview, creating it if it does not already exist.
+def get_dir_and_create_if_needed(path: PATH_TYPE, create_if_nonexistent: bool = True):
+    if create_if_nonexistent and not os.path.exists(path):
+        os.makedirs(path, mode=777, exist_ok=True)
+    return path
+
+
+def _get_user_dir(create_if_nonexistent: bool = False) -> PATH_TYPE:
+    """**DEPRECATED** Do not use this function to create new files. This is only used to move files from previous
+    version locations to new locations
     """
     global _USER_DIR
+    return get_dir_and_create_if_needed(_USER_DIR, create_if_nonexistent)
+
+
+def get_config_dir(create_if_nonexistent: bool = True) -> PATH_TYPE:
+    """
+    The directory where os-specific configurations are stored.
+
+    Returns the directory string, creating it if it does not already exist.
+    """
+    global _CONFIG_DIR
+    return get_dir_and_create_if_needed(_CONFIG_DIR, create_if_nonexistent)
+
+
+def get_app_dir(create_if_nonexistent: bool = True) -> PATH_TYPE:
+    """
+    The directory where the os-specific app data is stored.
+
+    Returns the directory string, creating it if it does not already exist.
+    """
+    global _APP_DATA_DIR
+    return get_dir_and_create_if_needed(_APP_DATA_DIR, create_if_nonexistent)
+
+
+def get_app_dir_versioned(create_if_nonexistent: bool = True) -> PATH_TYPE:
+    """
+    The directory where the os-specific app data is stored.
 
-    if create_if_nonexistent and not os.path.exists(_USER_DIR):
-            os.mkdir(_USER_DIR)
+    Returns the directory string, creating it if it does not already exist.
+    """
+    global _APP_VERS_DIR
+    return get_dir_and_create_if_needed(_APP_VERS_DIR, create_if_nonexistent)
+
+
+def get_log_dir(create_if_nonexistent: bool = True) -> PATH_TYPE:
+    """
+    The directory where the os-specific logs are stored.
+
+    Returns the directory string, creating it if it does not already exist.
+    """
+    global _LOG_DIR
+    return get_dir_and_create_if_needed(_LOG_DIR, create_if_nonexistent)
+
+
+def get_cache_dir(create_if_nonexistent: bool = True) -> PATH_TYPE:
+    """
+    The directory where the os-specific cache is stored.
+
+    Returns the directory string, creating it if it does not already exist.
+    """
+    global _CACHE_DIR
+    return get_dir_and_create_if_needed(_CACHE_DIR, create_if_nonexistent)
+
+
+def get_plugin_dir(create_if_nonexistent: bool = True) -> PATH_TYPE:
+    """
+    The directory where the os-specific cache is stored.
+
+    Returns the directory string, creating it if it does not already exist.
+    """
+    app_dir = get_app_dir(create_if_nonexistent)
+    return get_dir_and_create_if_needed(Path(app_dir, 'plugin_models'), create_if_nonexistent)
 
-    return _USER_DIR
 
+def copy_old_files_to_new_location():
+    """Run at startup, check to see if files in the old locations exist and move them if they haven't already."""
+    # Copy the old log to the new location
+    user_dir = Path(_get_user_dir())
+    old_sasview_usr_dir = user_dir / '.sasview'
+    old_log = user_dir / 'sasview.log'
+    new_log = Path(get_log_dir()) / 'sasview.log'
+    if old_log.exists() and not new_log.exists():
+        shutil.copy2(old_log, new_log)
+    # Copy plugin models to new location
+    old_plugins = old_sasview_usr_dir / 'plugin_models'
+    new_plugins = Path(get_plugin_dir())
+    if old_plugins.exists():
+        files = [f for f in os.listdir(old_plugins) if os.path.isfile(os.path.join(old_plugins, f))]
+        for file in files:
+            if not Path(new_plugins, file).exists():
+                shutil.copy2(Path(old_plugins, file), Path(new_plugins, file))
+    # Copy config file over
+    new_config_dir = Path(get_config_dir())
+    config_name = f'config-{sasview_version.split(".")[0]}'
+    old_config = old_sasview_usr_dir / config_name
+    new_config = new_config_dir / config_name
+    print(old_config.exists())
path: src/sas/system/user.py
user: login: pkienzle
body: remove debug statements
created_at: 2025-02-20T19:47:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,18 +1,122 @@
 import os
+import shutil
+from pathlib import Path
+from platformdirs import PlatformDirs
+from sas.system.version import __version__ as sasview_version
 
-_USER_DIR = os.path.join(os.path.expanduser("~"), '.sasview')
+# Create separate versioned and unversioned file locations
+PLATFORM_DIRS_VERSIONED = PlatformDirs("SasView", "sasview", version=sasview_version)
+PLATFORM_DIRS_UNVERSIONED = PlatformDirs("SasView", "sasview")
+# Deprecated path
+_USER_DIR = Path(os.path.join(os.path.expanduser("~")))
+# OS agnostic pathing
+_APP_DATA_DIR = PLATFORM_DIRS_UNVERSIONED.user_data_dir
+_APP_VERS_DIR = PLATFORM_DIRS_VERSIONED.user_data_dir
+_CACHE_DIR = PLATFORM_DIRS_VERSIONED.user_cache_dir
+_CONFIG_DIR = PLATFORM_DIRS_UNVERSIONED.user_config_dir
+_LOG_DIR = PLATFORM_DIRS_UNVERSIONED.user_log_dir
 
+PATH_TYPE = [Path, os.path, str]
 
-def get_user_dir(create_if_nonexistent=True):
-    """
-    The directory where the per-user configuration is stored.
 
-    Returns ~/.sasview, creating it if it does not already exist.
+def get_dir_and_create_if_needed(path: PATH_TYPE, create_if_nonexistent: bool = True):
+    path = Path(path)
+    if create_if_nonexistent and not os.path.exists(path):
+        path.mkdir(parents=True, exist_ok=True)
+        path.chmod(764)
path: src/sas/system/user.py
user: login: jamescrake-merani
body: Why did you choose this specific mode for `chmod`? When I comment it out (and delete the directories created before so it creates them from scratch), I don't get the permission error anymore. But if this permission is needed for something else then it might break something down the line. In any case, we'll probably have to change it.
created_at: 2025-04-01T11:46:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,18 +1,122 @@
 import os
+import shutil
+from pathlib import Path
+from platformdirs import PlatformDirs
+from sas.system.version import __version__ as sasview_version
 
-_USER_DIR = os.path.join(os.path.expanduser("~"), '.sasview')
+# Create separate versioned and unversioned file locations
+PLATFORM_DIRS_VERSIONED = PlatformDirs("SasView", "sasview", version=sasview_version)
+PLATFORM_DIRS_UNVERSIONED = PlatformDirs("SasView", "sasview")
+# Deprecated path
+_USER_DIR = Path(os.path.join(os.path.expanduser("~")))
+# OS agnostic pathing
+_APP_DATA_DIR = PLATFORM_DIRS_UNVERSIONED.user_data_dir
+_APP_VERS_DIR = PLATFORM_DIRS_VERSIONED.user_data_dir
+_CACHE_DIR = PLATFORM_DIRS_VERSIONED.user_cache_dir
+_CONFIG_DIR = PLATFORM_DIRS_UNVERSIONED.user_config_dir
+_LOG_DIR = PLATFORM_DIRS_UNVERSIONED.user_log_dir
 
+PATH_TYPE = [Path, os.path, str]
 
-def get_user_dir(create_if_nonexistent=True):
-    """
-    The directory where the per-user configuration is stored.
 
-    Returns ~/.sasview, creating it if it does not already exist.
+def get_dir_and_create_if_needed(path: PATH_TYPE, create_if_nonexistent: bool = True):
+    path = Path(path)
+    if create_if_nonexistent and not os.path.exists(path):
+        path.mkdir(parents=True, exist_ok=True)
+        path.chmod(764)
path: src/sas/system/user.py
user: login: krzywon
body: No specific reason. I'll remove this line and see if that fixes things.
created_at: 2025-04-01T12:41:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,18 +1,122 @@
 import os
+import shutil
+from pathlib import Path
+from platformdirs import PlatformDirs
+from sas.system.version import __version__ as sasview_version
 
-_USER_DIR = os.path.join(os.path.expanduser("~"), '.sasview')
+# Create separate versioned and unversioned file locations
+PLATFORM_DIRS_VERSIONED = PlatformDirs("SasView", "sasview", version=sasview_version)
+PLATFORM_DIRS_UNVERSIONED = PlatformDirs("SasView", "sasview")
+# Deprecated path
+_USER_DIR = Path(os.path.join(os.path.expanduser("~")))
+# OS agnostic pathing
+_APP_DATA_DIR = PLATFORM_DIRS_UNVERSIONED.user_data_dir
+_APP_VERS_DIR = PLATFORM_DIRS_VERSIONED.user_data_dir
+_CACHE_DIR = PLATFORM_DIRS_VERSIONED.user_cache_dir
+_CONFIG_DIR = PLATFORM_DIRS_UNVERSIONED.user_config_dir
+_LOG_DIR = PLATFORM_DIRS_UNVERSIONED.user_log_dir
 
+PATH_TYPE = [Path, os.path, str]
 
-def get_user_dir(create_if_nonexistent=True):
-    """
-    The directory where the per-user configuration is stored.
 
-    Returns ~/.sasview, creating it if it does not already exist.
+def get_dir_and_create_if_needed(path: PATH_TYPE, create_if_nonexistent: bool = True):
+    path = Path(path)
+    if create_if_nonexistent and not os.path.exists(path):
+        path.mkdir(parents=True, exist_ok=True)
+        path.chmod(764)
path: src/sas/system/user.py
user: login: krzywon
body: I was trying to be too clever and removing the permissions solved the error. Try again when you get a chance.
created_at: 2025-04-01T13:24:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,93 @@
+.. sasview_files.rst
+
+.. Initial Draft: J Krzywon, Apr 2025
+.. Last Updated: J Krzywon, Apr. 7, 2025
+
+.. _UserFiles:
+
+SasView User Files
+==================
+
+During the installation process and while running the app, SasView creates a number of files that are stored outside
+the install location on the end users computer. These files include configurations, plugin models, compiled plugins,
+documentation, and example files. Please refer to the specific section to find where each type of file is stored.
+
+File paths that start with the '~' character refer to the user directory, which is typically `C:\\Users\<username>` in
+Windows, `/Users/<username>` in MacOS, and `/home/<username>` in most Linux distributions.
+
+
+:ref:`Plugin_Files`, :ref:`Config_Files`, :ref:`Log_Files`, :ref:`Documentation`, :ref:`Example_Data`
+
+.. _Config_Files:
+
+Configuration Files
+--------------------
+SasView stores two types of config file on the user disk, a general configuration file for each major version of sasview,
+and a categories file that allows the end user to change how their models are organized in the fitting perspective. The
+general configuration file, config-<v>.json is a json file that stores a mapping of config variables to their updated values.
+This file is only generated and stored locally if something in the :ref:`Preferences` panel has been modified. The categories
+file, categories.json, stores the user-preferred model organization, allowing a user to add model categories, move models
+into new categories, and not show certain models. More information on this feature is availabe in :ref:`Category_Manager`.
+
+OS-specific file locations for configuration files:
+ - Windows: ~/AppData/Local/sasview/SasView/
+ - MacOS: ~/Library/Application Support/SasView/
+ - Linux: <TODO>
path: src/sas/qtgui/MainWindow/media/sasview_files.rst
user: login: jamescrake-merani
body: ~/.config/SasView/
created_at: 2025-04-08T15:04:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,93 @@
+.. sasview_files.rst
+
+.. Initial Draft: J Krzywon, Apr 2025
+.. Last Updated: J Krzywon, Apr. 7, 2025
+
+.. _UserFiles:
+
+SasView User Files
+==================
+
+During the installation process and while running the app, SasView creates a number of files that are stored outside
+the install location on the end users computer. These files include configurations, plugin models, compiled plugins,
+documentation, and example files. Please refer to the specific section to find where each type of file is stored.
+
+File paths that start with the '~' character refer to the user directory, which is typically `C:\\Users\<username>` in
+Windows, `/Users/<username>` in MacOS, and `/home/<username>` in most Linux distributions.
+
+
+:ref:`Plugin_Files`, :ref:`Config_Files`, :ref:`Log_Files`, :ref:`Documentation`, :ref:`Example_Data`
+
+.. _Config_Files:
+
+Configuration Files
+--------------------
+SasView stores two types of config file on the user disk, a general configuration file for each major version of sasview,
+and a categories file that allows the end user to change how their models are organized in the fitting perspective. The
+general configuration file, config-<v>.json is a json file that stores a mapping of config variables to their updated values.
+This file is only generated and stored locally if something in the :ref:`Preferences` panel has been modified. The categories
+file, categories.json, stores the user-preferred model organization, allowing a user to add model categories, move models
+into new categories, and not show certain models. More information on this feature is availabe in :ref:`Category_Manager`.
+
+OS-specific file locations for configuration files:
+ - Windows: ~/AppData/Local/sasview/SasView/
+ - MacOS: ~/Library/Application Support/SasView/
+ - Linux: <TODO>
+
+.. _Log_Files:
+
+Log Files
+---------
+SasView creates a time-stamped log file that includes all messages entered into the log explorer, as well as a number of
+other messages related to application startup and shutdown.
+
+OS-specific file locations for log files:
+ - Windows: ~/AppData/Local/sasview/SasView/Logs/
+ - MacOS: ~/Library/Logs/SasView/
+ - Linux: <TODO>
path: src/sas/qtgui/MainWindow/media/sasview_files.rst
user: login: jamescrake-merani
body: ~/.local/state/SasView
created_at: 2025-04-08T15:05:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,93 @@
+.. sasview_files.rst
+
+.. Initial Draft: J Krzywon, Apr 2025
+.. Last Updated: J Krzywon, Apr. 7, 2025
+
+.. _UserFiles:
+
+SasView User Files
+==================
+
+During the installation process and while running the app, SasView creates a number of files that are stored outside
+the install location on the end users computer. These files include configurations, plugin models, compiled plugins,
+documentation, and example files. Please refer to the specific section to find where each type of file is stored.
+
+File paths that start with the '~' character refer to the user directory, which is typically `C:\\Users\<username>` in
+Windows, `/Users/<username>` in MacOS, and `/home/<username>` in most Linux distributions.
+
+
+:ref:`Plugin_Files`, :ref:`Config_Files`, :ref:`Log_Files`, :ref:`Documentation`, :ref:`Example_Data`
+
+.. _Config_Files:
+
+Configuration Files
+--------------------
+SasView stores two types of config file on the user disk, a general configuration file for each major version of sasview,
+and a categories file that allows the end user to change how their models are organized in the fitting perspective. The
+general configuration file, config-<v>.json is a json file that stores a mapping of config variables to their updated values.
+This file is only generated and stored locally if something in the :ref:`Preferences` panel has been modified. The categories
+file, categories.json, stores the user-preferred model organization, allowing a user to add model categories, move models
+into new categories, and not show certain models. More information on this feature is availabe in :ref:`Category_Manager`.
+
+OS-specific file locations for configuration files:
+ - Windows: ~/AppData/Local/sasview/SasView/
+ - MacOS: ~/Library/Application Support/SasView/
+ - Linux: <TODO>
+
+.. _Log_Files:
+
+Log Files
+---------
+SasView creates a time-stamped log file that includes all messages entered into the log explorer, as well as a number of
+other messages related to application startup and shutdown.
+
+OS-specific file locations for log files:
+ - Windows: ~/AppData/Local/sasview/SasView/Logs/
+ - MacOS: ~/Library/Logs/SasView/
+ - Linux: <TODO>
+
+.. _Plugin_Files:
+
+Plugin Files
+------------
+Plugin models, whether they be written by the user (:ref:`Writing_a_Plugin`), downloaded from the model marketplace
+(`Model Marketplace <https://marketplace.sasview.org/>`_), or sent from another SasView user, should be stored in a
+specific location to allow SasView to find them. During normal fitting operations, models are compiled and the compiled
+files are stored in a parallel location.
+
+OS-specific file locations for plugin files:
+ - Windows:
+    - Plugin Models: ~/AppData/Local/sasview/SasView/plugin_models/
+    - Compiled Models: ~/AppData/Local/sasview/SasView/compiled_models/
+ - MacOS:
+    - Plugin Models: ~/Library/Application Support/SasView/plugin_models/
+    - Compiled Models: ~/Library/Application Support/SasView/compiled_models/
+ - Linux:
+    - Plugin Models: <TODO>
path: src/sas/qtgui/MainWindow/media/sasview_files.rst
user: login: jamescrake-merani
body: Plugin models: ~/.local/share/SasView/plugin_models
Compiled models: ~/.local/share/SasView/compiled_models
created_at: 2025-04-09T11:02:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,93 @@
+.. sasview_files.rst
+
+.. Initial Draft: J Krzywon, Apr 2025
+.. Last Updated: J Krzywon, Apr. 7, 2025
+
+.. _UserFiles:
+
+SasView User Files
+==================
+
+During the installation process and while running the app, SasView creates a number of files that are stored outside
+the install location on the end users computer. These files include configurations, plugin models, compiled plugins,
+documentation, and example files. Please refer to the specific section to find where each type of file is stored.
+
+File paths that start with the '~' character refer to the user directory, which is typically `C:\\Users\<username>` in
+Windows, `/Users/<username>` in MacOS, and `/home/<username>` in most Linux distributions.
+
+
+:ref:`Plugin_Files`, :ref:`Config_Files`, :ref:`Log_Files`, :ref:`Documentation`, :ref:`Example_Data`
+
+.. _Config_Files:
+
+Configuration Files
+--------------------
+SasView stores two types of config file on the user disk, a general configuration file for each major version of sasview,
+and a categories file that allows the end user to change how their models are organized in the fitting perspective. The
+general configuration file, config-<v>.json is a json file that stores a mapping of config variables to their updated values.
+This file is only generated and stored locally if something in the :ref:`Preferences` panel has been modified. The categories
+file, categories.json, stores the user-preferred model organization, allowing a user to add model categories, move models
+into new categories, and not show certain models. More information on this feature is availabe in :ref:`Category_Manager`.
+
+OS-specific file locations for configuration files:
+ - Windows: ~/AppData/Local/sasview/SasView/
+ - MacOS: ~/Library/Application Support/SasView/
+ - Linux: <TODO>
+
+.. _Log_Files:
+
+Log Files
+---------
+SasView creates a time-stamped log file that includes all messages entered into the log explorer, as well as a number of
+other messages related to application startup and shutdown.
+
+OS-specific file locations for log files:
+ - Windows: ~/AppData/Local/sasview/SasView/Logs/
+ - MacOS: ~/Library/Logs/SasView/
+ - Linux: <TODO>
+
+.. _Plugin_Files:
+
+Plugin Files
+------------
+Plugin models, whether they be written by the user (:ref:`Writing_a_Plugin`), downloaded from the model marketplace
+(`Model Marketplace <https://marketplace.sasview.org/>`_), or sent from another SasView user, should be stored in a
+specific location to allow SasView to find them. During normal fitting operations, models are compiled and the compiled
+files are stored in a parallel location.
+
+OS-specific file locations for plugin files:
+ - Windows:
+    - Plugin Models: ~/AppData/Local/sasview/SasView/plugin_models/
+    - Compiled Models: ~/AppData/Local/sasview/SasView/compiled_models/
+ - MacOS:
+    - Plugin Models: ~/Library/Application Support/SasView/plugin_models/
+    - Compiled Models: ~/Library/Application Support/SasView/compiled_models/
+ - Linux:
+    - Plugin Models: <TODO>
+    - Compiled Models: <TODO>
+
+.. _Documentation:
+
+Documentation
+-------------
+SasView moves its documentation into a user location when launching the app, if the documentation does not already exist.
+Each version of SasView has its own documentation that may be different between versions, so a separate directory is used
+for each version of the doc files.
+
+OS-specific file locations for documentation:
+ - Windows: ~/AppData/Local/sasview/SasView/<sasview.version>/doc/
+ - MacOS: ~/Library/Application Support/SasView/<sasview.version>/doc/
+ - Linux: <TODO>
path: src/sas/qtgui/MainWindow/media/sasview_files.rst
user: login: jamescrake-merani
body: ~/.local/share/SasView/6.0.1/doc/
created_at: 2025-04-09T11:03:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,101 @@
+.. sasview_files.rst
+
+.. Initial Draft: J Krzywon, Apr 2025
+.. Last Updated: J Krzywon, Apr. 9, 2025
+
+.. _UserFiles:
+
+SasView User Files
+==================
+
+During the installation process and while running the app, SasView creates a number of files that are stored outside
+the install location on the end users computer. These files include configurations, plugin models, compiled plugins,
+documentation, and example files. Please refer to the specific section to find where each type of file is stored.
+
+File paths that start with the '~' character refer to the user directory, which is typically `C:\\\\Users\\<username>` in
path: src/sas/qtgui/MainWindow/media/sasview_files.rst
user: login: jamescrake-merani
body: The amount of backslashes on this directory doesn't look right to me.
created_at: 2025-04-09T13:41:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,101 @@
+.. sasview_files.rst
+
+.. Initial Draft: J Krzywon, Apr 2025
+.. Last Updated: J Krzywon, Apr. 9, 2025
+
+.. _UserFiles:
+
+SasView User Files
+==================
+
+During the installation process and while running the app, SasView creates a number of files that are stored outside
+the install location on the end users computer. These files include configurations, plugin models, compiled plugins,
+documentation, and example files. Please refer to the specific section to find where each type of file is stored.
+
+File paths that start with the '~' character refer to the user directory, which is typically `C:\\\\Users\\<username>` in
+Windows, `/Users/<username>` in MacOS, and `/home/<username>` in most Linux distributions.
+
+This document is specific to v6.1.0 and beyond. Previous versions of SasView stored user documents in the
+`~/.sasview` directory.
+
+**For users who wish to use versions both newer and older than v6.1, do not delete the contents
+of the `~/.sasview` directory.**
+
+
+:ref:`Plugin_Files`, :ref:`Config_Files`, :ref:`Log_Files`, :ref:`Documentation`
+
+.. , :ref:`Example_Data`
+
+.. _Config_Files:
+
+Configuration Files
+--------------------
+SasView stores two types of config file on the user disk, a general configuration file for each major version of sasview,
+and a categories file that allows the end user to change how their models are organized in the fitting perspective. The
+general configuration file, config-<v>.json is a json file that stores a mapping of config variables to their updated values.
+This file is only generated and stored locally if something in the :ref:`Preferences` panel has been modified. The categories
+file, categories.json, stores the user-preferred model organization, allowing a user to add model categories, move models
+into new categories, and not show certain models. More information on this feature is available in :ref:`Category_Manager`.
+
+OS-specific file locations for configuration files:
+ - Windows: ~/AppData/Local/sasview/SasView/
+ - MacOS: ~/Library/Application Support/SasView/
+ - Linux: ~/.config/SasView/
+
+.. _Log_Files:
+
+Log Files
+---------
+SasView creates a time-stamped log file that includes all messages entered into the log explorer, as well as a number of
+other messages related to application startup and shutdown.
+
+OS-specific file locations for log files:
+ - Windows: ~/AppData/Local/sasview/SasView/Logs/
+ - MacOS: ~/Library/Logs/SasView/
+ - Linux: ~/.local/state/SasView/
+
+.. _Plugin_Files:
+
+Plugin Files
+------------
+Plugin models, whether they be written by the user (:ref:`Writing_a_Plugin`), downloaded from the model marketplace
+(`Model Marketplace <https://marketplace.sasview.org/>`_), or sent from another SasView user, should be stored in a
+specific location to allow SasView to find them. During normal fitting operations, models are compiled and the compiled
+files are stored in a parallel location.
+
+OS-specific file locations for plugin files:
path: src/sas/qtgui/MainWindow/media/sasview_files.rst
user: login: jamescrake-merani
body: I'm not sure whether it would be clear to someone reading this what the distinction is between the 'Plugin Models', and 'Compiled Models' directories. Furthermore, I think saying 'OS-speciific file locations for **plugin** files' is a bit confusing because it might imply that you need to put stuff into the `compiled_models` directory to add a plugin model.
created_at: 2025-04-09T13:50:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,101 @@
+.. sasview_files.rst
+
+.. Initial Draft: J Krzywon, Apr 2025
+.. Last Updated: J Krzywon, Apr. 9, 2025
+
+.. _UserFiles:
+
+SasView User Files
+==================
+
+During the installation process and while running the app, SasView creates a number of files that are stored outside
+the install location on the end users computer. These files include configurations, plugin models, compiled plugins,
+documentation, and example files. Please refer to the specific section to find where each type of file is stored.
+
+File paths that start with the '~' character refer to the user directory, which is typically `C:\\\\Users\\<username>` in
path: src/sas/qtgui/MainWindow/media/sasview_files.rst
user: login: krzywon
body: A single backslash acts as an escape character, so two consecutive backslashes result in a single backslash in the built documentation.

https://docutils.sourceforge.io/docs/user/rst/quickref.html#escaping
created_at: 2025-04-09T15:17:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,101 @@
+.. sasview_files.rst
+
+.. Initial Draft: J Krzywon, Apr 2025
+.. Last Updated: J Krzywon, Apr. 9, 2025
+
+.. _UserFiles:
+
+SasView User Files
+==================
+
+During the installation process and while running the app, SasView creates a number of files that are stored outside
+the install location on the end users computer. These files include configurations, plugin models, compiled plugins,
+documentation, and example files. Please refer to the specific section to find where each type of file is stored.
+
+File paths that start with the '~' character refer to the user directory, which is typically `C:\\\\Users\\<username>` in
path: src/sas/qtgui/MainWindow/media/sasview_files.rst
user: login: jamescrake-merani
body: Ah I see. That makes sense.
created_at: 2025-04-09T16:34:50Z
author_association: CONTRIBUTOR

PR comments:
user: login: rozyczko
created_at: 2025-03-28T06:46:18Z
body: Shouldn't this be now against `main`?
author_association: MEMBER
user: login: krzywon
created_at: 2025-03-28T12:32:32Z
body: > Shouldn't this be now against `main`?

Yes. I'll rebase later today
author_association: CONTRIBUTOR
user: login: jamescrake-merani
created_at: 2025-04-02T09:18:36Z
body: I've just noticed this is still marked as a draft. Is there any work left do on this?
author_association: CONTRIBUTOR
user: login: krzywon
created_at: 2025-04-02T11:40:14Z
body: > I've just noticed this is still marked as a draft. Is there any work left do on this?

Two items left:

* Documenting new user file locations (Required)
* Adding methods and GUI hooks to open user file locations (I strongly suggest this be added now that files are in different places)


author_association: CONTRIBUTOR
user: login: pkienzle
created_at: 2025-04-03T17:39:31Z
body: This is making pathing os specific rather than os agnostic. The previous system didn't care which operating system it was using.

The plugins were copied to `~/Library/Application Support/SasView/plugin_models`, including weights.
[edit: weights were correctly copied to `~/Library/Application Support/SasView/weights`]

Logs are appended to ~/Library/Logs/SasView/sasview.log

This file already had logs from existing versions of python.

**Bug**: The new log entries are missing the date portion of the timestamp.

**Feature request**: Show the path to the file on disk in the model editor window.

**Feature request**: Plugin polydispersity distributions are not appearing in the plugin manager.

**Bug**: On my intel mac I'm getting the following when I click "Load File" on the model editor window:
```python
12:25:42 : ERROR : sas.qtgui.Perspectives.Fitting.FittingWidget (FittingWidget.py:158) :: Traceback (most recent call last):
  File ".../sasview/src/sas/qtgui/Utilities/TabbedModelEditor.py", line 162, in onLoad
    filename = QtWidgets.QFileDialog.getOpenFileName(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../site-packages/qtpy/_utils.py", line 159, in _from_kwarg_name_to_kwarg_name_
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
TypeError: PySide6.QtWidgets.QFileDialog.getOpenFileName((<sas.qtgui.Utilities.TabbedModelEditor.TabbedModelEditor(0x600000510780, name="TabbedModelEditor") at 0x14f4acc80>, 'Open Plugin', PosixPath('~/Library/Application Support/SasView/plugin_models'), 'SasView Plugin Model (*.py)', None)) is wrong (missing signature)
```

**Feature request**: Store base doc tree in the application install directory. Only store patches in the user data.

The current Doc tree is 170 MB. If you end up with a new tree for each point release that's a lot of data.

In the unlikely case the user has edited the docs, will the edits show up when a new version is installed? The only way I see that happening is if the edits are represented as a patch file.

author_association: CONTRIBUTOR
user: login: krzywon
created_at: 2025-04-08T14:35:03Z
body: > This is making pathing os specific rather than os agnostic. The previous system didn't care which operating system it was using.

I updated the PR description.

> The plugins were copied to `~/Library/Application Support/SasView/plugin_models`, including weights. [edit: weights were correctly copied to `~/Library/Application Support/SasView/weights`]

Thanks, I updated the documentation with this.

> Logs are appended to ~/Library/Logs/SasView/sasview.log
>
> This file already had logs from existing versions of python.

The log file gets copied to the new directory on app launch, if no log exists in the new location.

> **Bug**: The new log entries are missing the date portion of the timestamp.

Fixed.

> **Feature request**: Show the path to the file on disk in the model editor window.
> 
> **Feature request**: Plugin polydispersity distributions are not appearing in the plugin manager.

Beyond the PR scope at this point.

> **Bug**: On my intel mac I'm getting the following when I click "Load File" on the model editor window:

Fixed.

> **Feature request**: Store base doc tree in the application install directory. Only store patches in the user data.

Another beyond the PR scope, though, noted.


author_association: CONTRIBUTOR