From 1d640c9887f97ff0f94f4fb631aea3728589abeb Mon Sep 17 00:00:00 2001 From: Guido Cella Date: Sun, 5 May 2024 18:11:00 +0200 Subject: [PATCH] player: normalize paths for resuming playback Paths like foo.mkv, ./foo.mkv .//foo.mkv, ../"$(basename "$PWD")"/foo.mkv, and C:\foo.mkv and C:/foo.mkv on Windows, use different config files for resuming playback, so if you quit-watch-later and later play the same file with a different path, mpv does not resume playback. This commit normalizes the paths on Unix to fix this. --- misc/path_utils.c | 53 +++++++++++++++++++++++++++++++++++++++++++- player/configfiles.c | 44 +++++++++++++++--------------------- test/paths.c | 34 ++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 27 deletions(-) diff --git a/misc/path_utils.c b/misc/path_utils.c index 14b4a97031..7fbd1084b8 100644 --- a/misc/path_utils.c +++ b/misc/path_utils.c @@ -151,10 +151,61 @@ char *mp_getcwd(void *talloc_ctx) char *mp_normalize_path(void *talloc_ctx, const char *path) { + if (!path) + return NULL; + if (mp_is_url(bstr0(path))) return talloc_strdup(talloc_ctx, path); - return mp_path_join(talloc_ctx, mp_getcwd(talloc_ctx), path); + if (!mp_path_is_absolute(bstr0(path))) { + char *cwd = mp_getcwd(talloc_ctx); + if (!cwd) + return NULL; + path = mp_path_join(talloc_ctx, cwd, path); + } + +#if HAVE_DOS_PATHS + return talloc_strdup(talloc_ctx, path); +#else + char *result = talloc_strdup(talloc_ctx, ""); + const char *next; + const char *end = path + strlen(path); + + for (const char *ptr = path; ptr < end; ptr = next + 1) { + next = memchr(ptr, '/', end - ptr); + if (next == NULL) + next = end; + + switch (next - ptr) { + case 0: + continue; + case 1: + if (ptr[0] == '.') + continue; + break; + case 2: + // Normalizing symlink/.. results in a wrong path: if the + // current working directory is /tmp/foo, and it is a symlink to + // /usr/bin, mpv ../file.mkv opens /usr/file.mkv, so we can't + // normalize the path to /tmp/file.mkv. Resolve symlinks to fix + // this. Otherwise we don't use realpath so users can use + // symlinks e.g. to hide how media files are distributed over + // real storage and move them while still resuming playback as + // long as the symlinked path doesn't change. + if (ptr[0] == '.' && ptr[1] == '.') { + char *tmp_result = realpath(path, NULL); + result = talloc_strdup(talloc_ctx, tmp_result); + free(tmp_result); + return result; + } + } + + result = talloc_strdup_append_buffer(result, "/"); + result = talloc_strndup_append_buffer(result, ptr, next - ptr); + } + + return result; +#endif } bool mp_path_exists(const char *path) diff --git a/player/configfiles.c b/player/configfiles.c index 2b94308baa..123867db7e 100644 --- a/player/configfiles.c +++ b/player/configfiles.c @@ -209,20 +209,16 @@ static char *mp_get_playback_resume_config_filename(struct MPContext *mpctx, struct MPOpts *opts = mpctx->opts; char *res = NULL; void *tmp = talloc_new(NULL); - const char *realpath = fname; - bstr bfname = bstr0(fname); - if (!mp_is_url(bfname)) { - if (opts->ignore_path_in_watch_later_config) { - realpath = mp_basename(fname); - } else { - char *cwd = mp_getcwd(tmp); - if (!cwd) - goto exit; - realpath = mp_path_join(tmp, cwd, fname); - } + const char *path = NULL; + if (opts->ignore_path_in_watch_later_config && !mp_is_url(bstr0(path))) { + path = mp_basename(fname); + } else { + path = mp_normalize_path(tmp, fname); + if (!path) + goto exit; } uint8_t md5[16]; - av_md5_sum(md5, realpath, strlen(realpath)); + av_md5_sum(md5, path, strlen(path)); char *conf = talloc_strdup(tmp, ""); for (int i = 0; i < 16; i++) conf = talloc_asprintf_append(conf, "%02X", md5[i]); @@ -312,6 +308,8 @@ void mp_write_watch_later_conf(struct MPContext *mpctx) goto exit; char *path = mp_normalize_path(ctx, cur->filename); + if (!path) + goto exit; struct demuxer *demux = mpctx->demuxer; @@ -391,26 +389,19 @@ exit: void mp_delete_watch_later_conf(struct MPContext *mpctx, const char *file) { - if (!file) { - struct playlist_entry *cur = mpctx->playing; - if (!cur) - return; - file = cur->filename; - if (!file) - return; - } + void *ctx = talloc_new(NULL); + char *path = mp_normalize_path(ctx, file ? file : mpctx->filename); + if (!path) + goto exit; - char *fname = mp_get_playback_resume_config_filename(mpctx, file); + char *fname = mp_get_playback_resume_config_filename(mpctx, path); if (fname) { unlink(fname); talloc_free(fname); } - if (mp_is_url(bstr0(file)) || mpctx->opts->ignore_path_in_watch_later_config) - return; - - void *ctx = talloc_new(NULL); - char *path = mp_normalize_path(ctx, file); + if (mp_is_url(bstr0(path)) || mpctx->opts->ignore_path_in_watch_later_config) + goto exit; bstr dir = mp_dirname(path); while (dir.len > 1 && dir.len < strlen(path)) { @@ -424,6 +415,7 @@ void mp_delete_watch_later_conf(struct MPContext *mpctx, const char *file) dir = mp_dirname(path); } +exit: talloc_free(ctx); } diff --git a/test/paths.c b/test/paths.c index 90c1ddf94a..e18c9dfb1c 100644 --- a/test/paths.c +++ b/test/paths.c @@ -26,12 +26,28 @@ static void test_abs(char *file, int line, bool abs, char *a) } } +static void test_normalize(char *file, int line, char *expected, char *path) +{ + void *ctx = talloc_new(NULL); + char *normalized = mp_normalize_path(ctx, path); + if (strcmp(normalized, expected)) { + printf("%s:%d: mp_normalize_path('%s') => %s, expected %s\n", + file, line, path, normalized, expected); + fflush(stdout); + abort(); + } + talloc_free(ctx); +} + #define TEST_JOIN(a, b, c) \ test_join(__FILE__, __LINE__, a, b, c); #define TEST_ABS(abs, a) \ test_abs(__FILE__, __LINE__, abs, a) +#define TEST_NORMALIZE(expected, path) \ + test_normalize(__FILE__, __LINE__, expected, path) + int main(void) { TEST_ABS(true, "/ab"); @@ -63,5 +79,23 @@ int main(void) TEST_JOIN("c:a", "b", "c:a/b"); TEST_JOIN("c:", "b", "c:b"); #endif + + TEST_NORMALIZE("https://foo", "https://foo"); + TEST_NORMALIZE("/foo", "/foo"); + + void *ctx = talloc_new(NULL); + bstr dst = bstr0(mp_getcwd(ctx)); + bstr_xappend(ctx, &dst, bstr0("/foo")); + TEST_NORMALIZE(dst.start, "foo"); + talloc_free(ctx); + +#if (!HAVE_DOS_PATHS) + TEST_NORMALIZE("/foo/bar", "/foo//bar"); + TEST_NORMALIZE("/foo/bar", "/foo///bar"); + TEST_NORMALIZE("/foo/bar", "/foo/bar/"); + TEST_NORMALIZE("/foo/bar", "/foo/./bar"); + TEST_NORMALIZE("/usr", "/usr/bin/.."); +#endif + return 0; }