push
This commit is contained in:
219
pl_env.h
Normal file
219
pl_env.h
Normal file
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright 2021 Patrick Gaskin
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_PL_ENV_H
|
||||
#define CMUS_PL_ENV_H
|
||||
|
||||
/**
|
||||
* ABOUT
|
||||
* This file contains functions related to the pl_env_vars configuration
|
||||
* option, which allows the library to be relocated by specifying a list of
|
||||
* environment variables to use instead of the base path they contain when
|
||||
* saving library/playlist and cache paths.
|
||||
*
|
||||
* CONFIGURATION
|
||||
* - options.h / pl_env_vars
|
||||
* The array of environment variables to substitute. These are checked in the
|
||||
* order they are specified in, and the first matching variable is used.
|
||||
* Note that it is safe for the configuration to be changed regardless of if
|
||||
* cmus is currently running, but cmus must be restarted for this to take full
|
||||
* effect.
|
||||
*
|
||||
* - options.c / get_pl_env_vars + set_pl_env_vars
|
||||
* Encodes and decodes the config entry to/from a comma-separated list.
|
||||
*
|
||||
* SUBSTITUTION
|
||||
* - cmus.c / save_playlist_cb
|
||||
* Where the in-memory library/playlists are written on exit.
|
||||
*
|
||||
* Paths are transformed into the versions with env var substitutions here to
|
||||
* allow tracks to be relocated based on the path in the env var.
|
||||
*
|
||||
* - job.c / handle_line
|
||||
* Where the on-disk library/playlists are parsed on startup.
|
||||
*
|
||||
* Paths are subsituted from environment variables here as the inverse of
|
||||
* save_playlist_cb.
|
||||
*
|
||||
* - cache.c / write_ti
|
||||
* Where the in-memory track_info's are converted into cache entries, which
|
||||
* are written to the on-disk cache file.
|
||||
*
|
||||
* Paths are transformed into the versions with env var substitutions here to
|
||||
* allow metadata (including the play count) to follow the track regardless of
|
||||
* its real on-disk location.
|
||||
*
|
||||
* - cache.c / cache_entry_to_ti
|
||||
* Where the on-disk cache entries are parsed into the in-memory hashtable of
|
||||
* read/write track_info's on startup.
|
||||
*
|
||||
* Paths are subsituted from environment variables here as the inverse of
|
||||
* write_ti.
|
||||
*
|
||||
* - cache.c / cache_get_ti
|
||||
* Where cmus reads the metadata for tracks when reading the library/playlists
|
||||
* at startup, or when the user adds a track.
|
||||
*
|
||||
* This is important because if this returns an error, the track is removed
|
||||
* from the library/playlist. Since we don't want the user to lose their
|
||||
* tracks if one of their environment variables in pl_env_vars was unset
|
||||
* mistakenly, we just return a dummy track if the filename still contains a
|
||||
* substitution.
|
||||
*
|
||||
* - player.c / _producer_play
|
||||
* Where track playback is started and errors are handled.
|
||||
*
|
||||
* If the error is due to a missing env var (see cache_get_ti above), we add
|
||||
* to the error message so the cause is clear.
|
||||
*
|
||||
* FAQ
|
||||
* - Why use '\x1F' as the delimiter?
|
||||
* 1. To prevent compatibility issues if the user happened to use unusual
|
||||
* characters in their filenames. Control characters like this one will not
|
||||
* generally be found in filenames on any platform.
|
||||
* 2. This is the ASCII "Unit Separator" control character, which is supposed
|
||||
* to be an (old-fashioned) invisible delimiter, which makes sense for our
|
||||
* purposes.
|
||||
* 3. Since it is invisible, we don't need to deal with everywhere it might be
|
||||
* printed to the UI if it is not substituted by pl_env_expand (i.e. if
|
||||
* the env var is missing or invalid).
|
||||
*
|
||||
* - Why not remove the substitution if the env var is missing or invalid?
|
||||
* 1. When combined with the invisibility of the delimiter, this allows us to
|
||||
* easily show the user the missing env var without needing to hook into
|
||||
* all of the UI printing/formatting code.
|
||||
* 2. See the part above about cache_get_ti for a technical reason why we
|
||||
* leave missing env var substitutions in.
|
||||
*
|
||||
* - What other approaches were considered?
|
||||
* 1. Using relative paths and resolving the filenames passed to input
|
||||
* plugins. This would have required rewriting the library/playlist and
|
||||
* cache code to handle relative paths, and would have resulted in a high
|
||||
* risk of regressions and taken a long time to implement.
|
||||
* 2. Wrapping the filesystem functions. This is simpler to implement than the
|
||||
* previous option, but even more risky.
|
||||
* 3. Same as this approach, but using native OS env var syntax. This would be
|
||||
* significantly more complex to implement, and is likely to cause
|
||||
* compatibility issues requiring manual intervention.
|
||||
* 4. Adding a configuration option for a base directory, and only storing
|
||||
* relative paths against that. This seems simple at first, but it's has
|
||||
* all the disadvantages of the aforementioned approaches, is more complex
|
||||
* to implement, makes it harder to handle configuration changes while cmus
|
||||
* is not running, and is a lot less flexible.
|
||||
*
|
||||
* - What are the advantages of this approach?
|
||||
* 1. This approach is mostly self-contained. There are very few places which
|
||||
* this needs to hook, and the mechanism is easy to reason about.
|
||||
* 2. This approach works well with cmus. It takes advantage of the way it
|
||||
* keeps all metadata in-memory during runtime.
|
||||
* 3. This approach does not have a risk of causing compatibility issues,
|
||||
* behaviour changes, or regressions for users who do not enable this
|
||||
* feature.
|
||||
* 4. This approach can handle configuration changes regardless of whether
|
||||
* cmus is running.
|
||||
* 5. This approach is easy to disable, simply by clearing the configuration
|
||||
* option. On the next exit, the paths will be restored to their
|
||||
* un-substituted values. Note that this will still work even if the path
|
||||
* in the environment variable does not exist, keeping the behaviour
|
||||
* consistent with how cmus normally works (the paths will be preserved as
|
||||
* long as they are also in the metadata cache, and an error will be
|
||||
* displayed on playback). This is possible because env var substitions
|
||||
* are always parsed at launch regardless of pl_env_vars.
|
||||
* 6. This approach does not require modifying input/output plugins.
|
||||
* 7. This approach is os-independent, and can even handle sharing libraries
|
||||
* with platforms using the backslash as a path separator.
|
||||
* 8. This approach does not interfere with the stream mechanism.
|
||||
* 9. This approach can handle multiple replacement paths (i.e. base
|
||||
* folders).
|
||||
* 10. This approach preserves library/playlist/cache backwards compatibility
|
||||
* while pl_env_vars is disabled.
|
||||
* 11. This approach results in behaviour comparable to without it.
|
||||
*
|
||||
* - What are the disadvantages of this approach?
|
||||
* 1. While the substitution works correctly and reliably, it is a somewhat
|
||||
* hacky method. Nevertheless, it is also the least intrusive method, which
|
||||
* is a significant advantage.
|
||||
* 2. Certain error messages will be somewhat misleading when an environment
|
||||
* variable is missing. This is mitigated by improving the error message
|
||||
* for the most common error, failed playback due to a nonexistent path
|
||||
* caused by a missing environment variable.
|
||||
* 3. It is not possible to implement a mechanism for fallback paths within
|
||||
* cmus for searching for individual tracks.
|
||||
*
|
||||
* - What are some potential uses of pl_env_vars?
|
||||
* 1. Syncing a $CMUS_HOME with multiple devices with different home folders.
|
||||
* 2. Syncing a $CMUS_HOME with multiple devices with one or more different
|
||||
* music paths.
|
||||
* 3. The above case, but also with a path which only exists on certain
|
||||
* devices.
|
||||
* 4. Easily relocating the cache/library/playlists while preserving metadata
|
||||
* including the play count. This can be done by setting pl_env_vars,
|
||||
* exiting cmus, updating the env var to the new path, starting cmus,
|
||||
* clearing pl_env_vars, and exiting cmus again.
|
||||
*/
|
||||
|
||||
/**
|
||||
* PL_ENV_DELIMITER surrounds env var substitutions at the beginning of paths.
|
||||
*/
|
||||
#define PL_ENV_DELIMITER '\x1F'
|
||||
|
||||
/**
|
||||
* pl_env_init initializes the environment variable cache used by pl_env_get. It
|
||||
* must be called before loading the library, playlists, or cache.
|
||||
*/
|
||||
void pl_env_init(void);
|
||||
|
||||
/**
|
||||
* pl_env_reduce checks the base path against the configured environment
|
||||
* variables, replaces the first match with a substitution, and returns a
|
||||
* malloc'd copy of the result. If there isn't any valid match or the path
|
||||
* already contains a substitution, a copy of the original path is returned
|
||||
* as-is.
|
||||
*/
|
||||
char *pl_env_reduce(const char *path);
|
||||
|
||||
/**
|
||||
* pl_env_expand returns a malloc'd copy of path, with the environment variable
|
||||
* substitution. The provided path must use forward slashes, and begin with a
|
||||
* slash or a substitution followed by a slash (which will always be true for
|
||||
* library paths within cmus). If the path does not have a substitution, the
|
||||
* original path is returned. If the environment variable does not exist or is
|
||||
* invalid, the original path is also returned.
|
||||
*/
|
||||
char *pl_env_expand(const char *path);
|
||||
|
||||
/**
|
||||
* pl_env_var returns a pointer to the start of the substituted environment
|
||||
* variable name, or NULL if it is not present. If a variable is present and
|
||||
* out_length is not NULL, it is set to the length of the variable name. The
|
||||
* remainder of the path will be at pl_env_var_remainder(path, out_length).
|
||||
*/
|
||||
const char *pl_env_var(const char *path, int *out_length);
|
||||
|
||||
/**
|
||||
* pl_env_var_remainder returns a pointer to the remainder of the path after the
|
||||
* substitution. See pl_env_var.
|
||||
*/
|
||||
const char *pl_env_var_remainder(const char *path, int length);
|
||||
|
||||
/**
|
||||
* pl_env_var_len returns the length of the substituted environment variable
|
||||
* name, if present. Otherwise, it returns 0.
|
||||
*/
|
||||
int pl_env_var_len(const char *path);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user