From 6a892d654941b75d5fbe2a501ccad0ebfa4be959 Mon Sep 17 00:00:00 2001 From: ShukeBta Date: Wed, 10 Jun 2026 12:47:39 +0800 Subject: [PATCH] fix: handle OpenList unicode mount paths --- internal/service/cloud/cloud_test.go | 12 +++++++++++ internal/service/cloud/clouddrive2.go | 25 ++++++++++++---------- internal/service/scraper.go | 30 ++++++++++++++++++--------- 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/internal/service/cloud/cloud_test.go b/internal/service/cloud/cloud_test.go index 723a5ba..432f91f 100644 --- a/internal/service/cloud/cloud_test.go +++ b/internal/service/cloud/cloud_test.go @@ -450,6 +450,18 @@ func TestOpenListRootURLDefaultsToDAV(t *testing.T) { } } +func TestOpenListURLForKeepsNonASCIIPathSingleEncoded(t *testing.T) { + p := newOpenList(map[string]any{"url": "http://example.test:5244/dav/"}, nil) + got := p.urlFor("/动画电影/爱宠大机密2 (2019) {tmdb-412117}") + if strings.Contains(got, "%25E") { + t.Fatalf("url is double-escaped: %s", got) + } + want := "http://example.test:5244/dav/%E5%8A%A8%E7%94%BB%E7%94%B5%E5%BD%B1/%E7%88%B1%E5%AE%A0%E5%A4%A7%E6%9C%BA%E5%AF%862%20%282019%29%20%7Btmdb-412117%7D" + if got != want { + t.Fatalf("url = %s, want %s", got, want) + } +} + func TestOpenListDAVStatusErrorIncludesBodyHint(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusMethodNotAllowed) diff --git a/internal/service/cloud/clouddrive2.go b/internal/service/cloud/clouddrive2.go index 078165a..a600365 100644 --- a/internal/service/cloud/clouddrive2.go +++ b/internal/service/cloud/clouddrive2.go @@ -251,18 +251,21 @@ func decorateDAVTransportError(name, target string, err error) error { func (p *cloudDrive2Provider) urlFor(remotePath string) string { u := *p.base - basePath := strings.TrimRight(u.EscapedPath(), "/") - segments := make([]string, 0) - if basePath != "" && basePath != "/" { - segments = append(segments, strings.Trim(basePath, "/")) - } - for _, part := range strings.Split(strings.Trim(normalizeCloudDAVPath(remotePath), "/"), "/") { - if part != "" { - segments = append(segments, url.PathEscape(part)) - } - } u.RawPath = "" - u.Path = "/" + strings.Join(segments, "/") + basePath := strings.TrimRight(u.Path, "/") + remote := strings.Trim(normalizeCloudDAVPath(remotePath), "/") + switch { + case basePath == "" || basePath == "/": + if remote == "" { + u.Path = "/" + } else { + u.Path = "/" + remote + } + case remote == "": + u.Path = basePath + default: + u.Path = basePath + "/" + remote + } return u.String() } diff --git a/internal/service/scraper.go b/internal/service/scraper.go index 04af41c..a297529 100644 --- a/internal/service/scraper.go +++ b/internal/service/scraper.go @@ -173,12 +173,15 @@ func (s *ScraperService) EnrichOne(ctx context.Context, m *model.Media) error { } seriesLike := mediaIsEpisodic(m, lib) + cloudMedia := isCloudMediaPath(m.Path) || (lib != nil && isCloudMediaPath(lib.Path)) var local *LocalMetadata - if found, err := ReadLocalMetadata(m.Path, lib.Path, seriesLike); err == nil && found != nil { - local = found - applyLocalMetadata(m, local) - } else if err != nil { - s.log.Warn("read local metadata before scrape failed", zap.String("media_id", m.ID), zap.Error(err)) + if !cloudMedia { + if found, err := ReadLocalMetadata(m.Path, lib.Path, seriesLike); err == nil && found != nil { + local = found + applyLocalMetadata(m, local) + } else if err != nil { + s.log.Warn("read local metadata before scrape failed", zap.String("media_id", m.ID), zap.Error(err)) + } } year := m.Year @@ -351,11 +354,14 @@ func (s *ScraperService) applyProviderMatch(ctx context.Context, m *model.Media, Updates(updates).Error; err != nil { return err } - if refreshed, err := s.repo.Media.FindByID(ctx, m.ID); err == nil && refreshed != nil { - if path, err := WriteMediaNFO(refreshed); err != nil { - s.log.Warn("write nfo after scrape failed", zap.String("media_id", m.ID), zap.Error(err)) - } else { - s.log.Debug("write nfo after scrape", zap.String("media_id", m.ID), zap.String("path", path)) + cloudMedia := isCloudMediaPath(m.Path) || (lib != nil && isCloudMediaPath(lib.Path)) + if !cloudMedia { + if refreshed, err := s.repo.Media.FindByID(ctx, m.ID); err == nil && refreshed != nil { + if path, err := WriteMediaNFO(refreshed); err != nil { + s.log.Warn("write nfo after scrape failed", zap.String("media_id", m.ID), zap.Error(err)) + } else { + s.log.Debug("write nfo after scrape", zap.String("media_id", m.ID), zap.String("path", path)) + } } } s.hub.Publish("scrape", map[string]any{ @@ -368,6 +374,10 @@ func (s *ScraperService) applyProviderMatch(ctx context.Context, m *model.Media, return nil } +func isCloudMediaPath(value string) bool { + return strings.HasPrefix(strings.ToLower(strings.TrimSpace(value)), "cloud://") +} + func (s *ScraperService) applyLocalMetadataMatch(ctx context.Context, m *model.Media, local *LocalMetadata) error { next := *m applyLocalMetadata(&next, local)