package downloader import ( "context" "path" "strings" ) // PathMap is one downloads folder under two names: where Minstrel has it // mounted (Local) and where the client sees it (Remote). The two usually run // in different containers. Every path crossing between them swaps one prefix // for the other. type PathMap struct { Local string Remote string } // Active says the two names differ, so there is something to translate. func (m PathMap) Active() bool { return m.Remote != "" && m.Local != "" && path.Clean(m.Remote) != path.Clean(m.Local) } // ToRemote is a Minstrel path as the client names it. func (m PathMap) ToRemote(p string) string { return swap(p, m.Local, m.Remote) } // ToLocal is a client's path as Minstrel names it. func (m PathMap) ToLocal(p string) string { return swap(p, m.Remote, m.Local) } // swap replaces the prefix from with to on segment boundaries // (/downloads2 is not inside /downloads). A path outside from is unchanged. func swap(p, from, to string) string { if p == "" || from == "" || to == "" { return p } from, to = path.Clean(from), path.Clean(to) clean := path.Clean(p) if clean == from { return to } if from == "/" { return path.Join(to, clean) } if rest, ok := strings.CutPrefix(clean, from+"/"); ok { return path.Join(to, rest) } return p } // Mapped wraps d so every path it is given or answers with is translated. // File indices and in-torrent paths need no translation. func Mapped(d Driver, m PathMap) Driver { if !m.Active() { return d } return &mapped{Driver: d, m: m} } type mapped struct { Driver m PathMap } func (c *mapped) AddTorrent(ctx context.Context, a Add) error { a.SavePath = c.m.ToRemote(a.SavePath) return c.Driver.AddTorrent(ctx, a) } func (c *mapped) Torrents(ctx context.Context, hashes ...string) ([]Torrent, error) { ts, err := c.Driver.Torrents(ctx, hashes...) for i := range ts { ts[i].SavePath = c.m.ToLocal(ts[i].SavePath) ts[i].ContentPath = c.m.ToLocal(ts[i].ContentPath) } return ts, err } func (c *mapped) SkipFiles(ctx context.Context, hash string, indices []int) error { fs, ok := c.Driver.(FileSkipper) if !ok { return ErrUnsupported } return fs.SkipFiles(ctx, hash, indices) } func (c *mapped) KeepFiles(ctx context.Context, hash string, indices []int) error { fs, ok := c.Driver.(FileSkipper) if !ok { return ErrUnsupported } return fs.KeepFiles(ctx, hash, indices) } func (c *mapped) Files(ctx context.Context, hash string) ([]File, error) { fl, ok := c.Driver.(FileLister) if !ok { return nil, ErrUnsupported } return fl.Files(ctx, hash) } func (c *mapped) FileStates(ctx context.Context, hash string) ([]FileState, error) { fp, ok := c.Driver.(FileProgress) if !ok { return nil, ErrUnsupported } return fp.FileStates(ctx, hash) }