Skip to content

Commit 308ecc7

Browse files
authored
Merge branch 'main' into fix/issue-617-systray-upstream
2 parents 05e8eee + 3d8d8a2 commit 308ecc7

4 files changed

Lines changed: 118 additions & 37 deletions

File tree

.github/workflows/codetests.yml

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,15 @@ jobs:
1212
os: [ubuntu, macos, windows]
1313
runs-on: ${{ matrix.os }}-latest
1414
steps:
15-
- uses: actions/checkout@v6
15+
- name: checkout PR head (pull_request_target)
16+
if: github.event_name == 'pull_request_target'
17+
uses: actions/checkout@v6
18+
with:
19+
repository: ${{ github.event.pull_request.head.repo.full_name }}
20+
ref: ${{ github.event.pull_request.head.sha }}
21+
- name: checkout branch (push)
22+
if: github.event_name == 'push'
23+
uses: actions/checkout@v6
1624
- uses: actions/setup-go@v6
1725
with:
1826
go-version: 'stable'
@@ -32,7 +40,15 @@ jobs:
3240
env:
3341
GOOS: ${{ matrix.os }}
3442
steps:
35-
- uses: actions/checkout@v6
43+
- name: checkout PR head (pull_request_target)
44+
if: github.event_name == 'pull_request_target'
45+
uses: actions/checkout@v6
46+
with:
47+
repository: ${{ github.event.pull_request.head.repo.full_name }}
48+
ref: ${{ github.event.pull_request.head.sha }}
49+
- name: checkout branch (push)
50+
if: github.event_name == 'push'
51+
uses: actions/checkout@v6
3652
- uses: actions/setup-go@v6
3753
with:
3854
go-version: 'stable'
@@ -53,7 +69,15 @@ jobs:
5369
env:
5470
GOOS: ${{ matrix.os }}
5571
steps:
56-
- uses: actions/checkout@v6
72+
- name: checkout PR head (pull_request_target)
73+
if: github.event_name == 'pull_request_target'
74+
uses: actions/checkout@v6
75+
with:
76+
repository: ${{ github.event.pull_request.head.repo.full_name }}
77+
ref: ${{ github.event.pull_request.head.sha }}
78+
- name: checkout branch (push)
79+
if: github.event_name == 'push'
80+
uses: actions/checkout@v6
5781
- uses: actions/setup-go@v6
5882
with:
5983
go-version: 'stable'

pkg/ui/dlgs.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func Entry(title, msg, val string) (string, bool, error) {
5454
if errors.Is(err, zenity.ErrCanceled) {
5555
return val, false, nil
5656
}
57+
5758
if err != nil {
5859
return value, false, fmt.Errorf("show entry dialog: %w", err)
5960
}

pkg/unpackerr/handlers.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ type Extract struct {
1818
Syncthing bool
1919
SplitFlac bool
2020
Retries uint
21-
Path string
21+
Path string // Local path (resolved for extraction on this host).
22+
OutputPath string // Original path from Starr app (may be UNC/remote — used for ManualImport).
2223
App starr.App
2324
URL string
2425
Updated time.Time
@@ -237,8 +238,7 @@ func (u *Unpackerr) handleXtractrCallback(resp *xtractr.Response) {
237238
u.Debugf("Extraction Finished: %d files in path: %s", len(files), files)
238239
u.updateQueueStatus(&newStatus{Name: resp.X.Name, Status: EXTRACTED, Resp: resp}, now, true)
239240

240-
if item != nil && item.App == starr.Lidarr && item.SplitFlac &&
241-
extractionHasFlacFiles(resp.NewFiles) {
241+
if item != nil && item.App == starr.Lidarr && item.SplitFlac && resp.Size > 0 {
242242
go u.importSplitFlacTracks(item, u.lidarrServerByURL(item.URL))
243243
}
244244
}
@@ -264,8 +264,25 @@ func (u *Unpackerr) getDownloadPath(outputPath string, app starr.App, title stri
264264
// Print the errors for each user-provided path.
265265
u.Debugf("%s: Errors encountered looking for %s path: %q", app, title, errs)
266266

267+
// The title often differs from the actual folder name (e.g. torrent names include genre tags).
268+
// Try the folder name from outputPath against configured paths — the folder name is the real
269+
// directory on disk. This also handles cross-platform setups where outputPath is a UNC/Windows
270+
// path but the configured paths are local Linux mounts of the same share.
267271
if outputPath != "" {
272+
outputFolder := filepath.Base(filepath.FromSlash(strings.ReplaceAll(outputPath, `\`, `/`)))
273+
if outputFolder != "" && outputFolder != "." && outputFolder != title {
274+
for _, path := range paths {
275+
candidate := filepath.Join(path, outputFolder)
276+
277+
if _, err := os.Stat(candidate); err == nil {
278+
u.Debugf("%s: Resolved via outputPath folder name: %s -> %s", app, outputPath, candidate)
279+
return candidate
280+
}
281+
}
282+
}
283+
268284
u.Debugf("%s: Configured paths do not exist; trying 'outputPath': %s", app, outputPath)
285+
269286
return outputPath
270287
}
271288

pkg/unpackerr/lidarr.go

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ package unpackerr
33
import (
44
"errors"
55
"path/filepath"
6+
"regexp"
67
"strings"
78
"time"
89

910
"golift.io/starr"
1011
"golift.io/starr/lidarr"
1112
)
1213

14+
// numberedTrackPattern matches filenames generated by CUE splitting: "NN - Title.flac".
15+
var numberedTrackPattern = regexp.MustCompile(`^\d{2,3} - .+\.flac$`)
16+
1317
// LidarrConfig represents the input data for a Lidarr server.
1418
type LidarrConfig struct {
1519
StarrConfig
@@ -102,6 +106,7 @@ func (u *Unpackerr) checkLidarrQueue(now time.Time) {
102106
Syncthing: server.Syncthing,
103107
SplitFlac: server.SplitFlac,
104108
Path: u.getDownloadPath(record.OutputPath, starr.Lidarr, record.Title, server.Paths),
109+
OutputPath: record.OutputPath,
105110
IDs: map[string]any{
106111
"title": record.Title,
107112
"artistId": record.ArtistID,
@@ -150,36 +155,32 @@ func (u *Unpackerr) lidarrServerByURL(url string) *LidarrConfig {
150155
return nil
151156
}
152157

153-
// extractionHasFlacFiles returns true if any path in files has a .flac extension.
154-
// Used to only trigger manual import after a FLAC+CUE split, not for e.g. zip-of-mp3s.
155-
func extractionHasFlacFiles(files []string) bool {
156-
for _, p := range files {
157-
if strings.HasSuffix(strings.ToLower(p), ".flac") {
158-
return true
159-
}
160-
}
161-
162-
return false
158+
// crossPlatformBase returns the filename from a path that may use either forward slashes
159+
// or backslashes as separators. On Linux, filepath.Base does not split on backslashes,
160+
// so Windows/UNC paths from Starr apps would return the entire path instead of the filename.
161+
func crossPlatformBase(path string) string {
162+
return filepath.Base(filepath.FromSlash(strings.ReplaceAll(path, `\`, `/`)))
163163
}
164164

165-
// filterManualImportToSplitTracks returns only outputs whose path is in the extract's NewFiles
166-
// (the split track files). This excludes the original FLAC file we split from, which Lidarr
167-
// would otherwise try and fail to import.
165+
// filterManualImportToSplitTracks returns only outputs whose filename matches a split track file.
166+
// This excludes the original audio file we split from, which Lidarr would otherwise try and fail
167+
// to import. Comparison uses basenames (filenames only) so it works across platforms — the extraction
168+
// host (e.g. Linux) may use different paths than the Starr app (e.g. Windows).
168169
func filterManualImportToSplitTracks(outputs []*lidarr.ManualImportOutput, item *Extract) []*lidarr.ManualImportOutput {
169170
if item == nil || item.Resp == nil || len(item.Resp.NewFiles) == 0 {
170-
return outputs
171+
return nil // Signal that we couldn't filter; caller should try fallback.
171172
}
172173

173-
splitPaths := make(map[string]struct{}, len(item.Resp.NewFiles))
174+
splitFiles := make(map[string]struct{}, len(item.Resp.NewFiles))
174175
for _, p := range item.Resp.NewFiles {
175-
splitPaths[filepath.Clean(p)] = struct{}{}
176+
splitFiles[strings.ToLower(crossPlatformBase(p))] = struct{}{}
176177
}
177178

178179
filtered := outputs[:0] // reusable memory
179180

180181
for _, out := range outputs {
181182
if out != nil && out.Path != "" {
182-
if _, ok := splitPaths[filepath.Clean(out.Path)]; ok {
183+
if _, ok := splitFiles[strings.ToLower(crossPlatformBase(out.Path))]; ok {
183184
filtered = append(filtered, out)
184185
}
185186
}
@@ -188,6 +189,43 @@ func filterManualImportToSplitTracks(outputs []*lidarr.ManualImportOutput, item
188189
return filtered
189190
}
190191

192+
// filterManualImportToNumberedTracks returns outputs whose filename matches the CUE-split
193+
// naming pattern (NN - Title.flac). This is a fallback when NewFiles tracking is unavailable
194+
// (e.g. after MoveFiles moves tracks back to the original folder).
195+
func filterManualImportToNumberedTracks(outputs []*lidarr.ManualImportOutput) []*lidarr.ManualImportOutput {
196+
filtered := make([]*lidarr.ManualImportOutput, 0, len(outputs))
197+
198+
for _, out := range outputs {
199+
if out != nil && out.Path != "" {
200+
base := crossPlatformBase(out.Path)
201+
if numberedTrackPattern.MatchString(base) {
202+
filtered = append(filtered, out)
203+
}
204+
}
205+
}
206+
207+
return filtered
208+
}
209+
210+
// filterSplitOutputs filters ManualImport outputs to only include split track files.
211+
// First tries matching against NewFiles (basename comparison), then falls back to the
212+
// CUE-split naming pattern (NN - Title.flac) when NewFiles is unavailable.
213+
func (u *Unpackerr) filterSplitOutputs(
214+
outputs []*lidarr.ManualImportOutput, item *Extract,
215+
) []*lidarr.ManualImportOutput {
216+
allOutputs := make([]*lidarr.ManualImportOutput, len(outputs))
217+
copy(allOutputs, outputs)
218+
219+
filtered := filterManualImportToSplitTracks(allOutputs, item)
220+
if len(filtered) > 0 {
221+
return filtered
222+
}
223+
224+
u.Printf("[Lidarr] No split track files matched via NewFiles for %s; trying numbered track pattern", item.Path)
225+
226+
return filterManualImportToNumberedTracks(allOutputs)
227+
}
228+
191229
// importSplitFlacTracks runs in a goroutine after a Lidarr FLAC+CUE split extraction completes.
192230
// It asks Lidarr for the manual import list for the extract folder and sends the ManualImport command
193231
// so Lidarr imports the split track files.
@@ -200,15 +238,20 @@ func (u *Unpackerr) importSplitFlacTracks(item *Extract, server *LidarrConfig) {
200238
downloadID, _ := item.IDs["downloadId"].(string)
201239
artistID, _ := item.IDs["artistId"].(int64)
202240

203-
params := &lidarr.ManualImportParams{
204-
Folder: item.Path,
241+
// Use OutputPath (the Starr app's view of the path) for ManualImport when available.
242+
// The extraction host may use a different mount/path than the Starr app (e.g., Linux vs Windows UNC).
243+
importFolder := item.Path
244+
if item.OutputPath != "" {
245+
importFolder = item.OutputPath
246+
}
247+
248+
outputs, err := server.ManualImport(&lidarr.ManualImportParams{
249+
Folder: importFolder,
205250
DownloadID: downloadID,
206251
ArtistID: artistID,
207252
FilterExistingFiles: false,
208253
ReplaceExistingFiles: true,
209-
}
210-
211-
outputs, err := server.ManualImport(params)
254+
})
212255
if err != nil {
213256
u.Errorf("[Lidarr] Manual import list failed for %s: %v", item.Path, err)
214257
return
@@ -219,12 +262,9 @@ func (u *Unpackerr) importSplitFlacTracks(item *Extract, server *LidarrConfig) {
219262
return
220263
}
221264

222-
// Exclude the original FLAC we split from; only import the split track files (item.Resp.NewFiles).
223-
// Lidarr returns every file in the folder including the source file, which will never import.
224-
outputs = filterManualImportToSplitTracks(outputs, item)
225-
265+
outputs = u.filterSplitOutputs(outputs, item)
226266
if len(outputs) == 0 {
227-
u.Printf("[Lidarr] No split track files to import (folder: %s); original FLAC excluded", item.Path)
267+
u.Printf("[Lidarr] No split track files to import (folder: %s)", item.Path)
228268
return
229269
}
230270

@@ -234,11 +274,10 @@ func (u *Unpackerr) importSplitFlacTracks(item *Extract, server *LidarrConfig) {
234274
return
235275
}
236276

237-
u.Debugf("[Lidarr] Sending manual import command: replaceExisting=%v, importMode=%q, files=%d: %v",
238-
cmd.ReplaceExistingFiles, cmd.ImportMode, len(cmd.Files), cmd.Files)
277+
u.Debugf("[Lidarr] Sending manual import command: replaceExisting=%v, importMode=%q, files=%d",
278+
cmd.ReplaceExistingFiles, cmd.ImportMode, len(cmd.Files))
239279

240-
_, err = server.SendManualImportCommand(cmd)
241-
if err != nil {
280+
if _, err = server.SendManualImportCommand(cmd); err != nil {
242281
u.Errorf("[Lidarr] Manual import command failed for %s: %v", item.Path, err)
243282
return
244283
}

0 commit comments

Comments
 (0)