-
-
Notifications
You must be signed in to change notification settings - Fork 104
feat(crossseed): allow custom instance link dirs #1574
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
s0up4200
wants to merge
7
commits into
develop
Choose a base branch
from
feat/instance-link-dir-override
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 3 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
41739e4
feat(crossseed): allow custom instance link dirs
s0up4200 1133b30
fix(database): update schema test for link dir name
s0up4200 97b9c33
fix(crossseed): persist and validate instance link dirs
s0up4200 b79cf09
fix(instances): validate link dir names on save
s0up4200 9b1409e
Merge branch 'develop' into feat/instance-link-dir-override
s0up4200 8113329
fix(instances): save reannounce settings atomically
s0up4200 92f5b19
test(api): fix instance store create call
s0up4200 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
internal/database/migrations/067_add_instance_link_dir_name.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| -- Copyright (c) 2026, s0up and the autobrr contributors. | ||
| -- SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| ALTER TABLE instances ADD COLUMN link_dir_name TEXT NOT NULL DEFAULT ''; | ||
|
|
||
| DROP VIEW IF EXISTS instances_view; | ||
| CREATE VIEW instances_view AS | ||
| SELECT | ||
| i.id, | ||
| n.value AS name, | ||
| h.value AS host, | ||
| u.value AS username, | ||
| i.password_encrypted, | ||
| bu.value AS basic_username, | ||
| i.basic_password_encrypted, | ||
| i.tls_skip_verify, | ||
| i.sort_order, | ||
| i.is_active, | ||
| i.has_local_filesystem_access, | ||
| i.use_hardlinks, | ||
| i.hardlink_base_dir, | ||
| i.hardlink_dir_preset, | ||
| i.link_dir_name, | ||
| i.use_reflinks, | ||
| i.fallback_to_regular_mode | ||
| FROM instances i | ||
| LEFT JOIN string_pool n ON i.name_id = n.id | ||
| LEFT JOIN string_pool h ON i.host_id = h.id | ||
| LEFT JOIN string_pool u ON i.username_id = u.id | ||
| LEFT JOIN string_pool bu ON i.basic_username_id = bu.id; |
31 changes: 31 additions & 0 deletions
31
internal/database/postgres_migrations/068_add_instance_link_dir_name.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| -- Copyright (c) 2026, s0up and the autobrr contributors. | ||
| -- SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| ALTER TABLE instances | ||
| ADD COLUMN link_dir_name TEXT NOT NULL DEFAULT ''; | ||
|
|
||
| DROP VIEW IF EXISTS instances_view; | ||
| CREATE VIEW instances_view AS | ||
| SELECT | ||
| i.id, | ||
| n.value AS name, | ||
| h.value AS host, | ||
| u.value AS username, | ||
| i.password_encrypted, | ||
| bu.value AS basic_username, | ||
| i.basic_password_encrypted, | ||
| i.tls_skip_verify, | ||
| i.sort_order, | ||
| i.is_active, | ||
| i.has_local_filesystem_access, | ||
| i.use_hardlinks, | ||
| i.hardlink_base_dir, | ||
| i.hardlink_dir_preset, | ||
| i.link_dir_name, | ||
| i.use_reflinks, | ||
| i.fallback_to_regular_mode | ||
| FROM instances i | ||
| LEFT JOIN string_pool n ON i.name_id = n.id | ||
| LEFT JOIN string_pool h ON i.host_id = h.id | ||
| LEFT JOIN string_pool u ON i.username_id = u.id | ||
| LEFT JOIN string_pool bu ON i.basic_username_id = bu.id; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| // Copyright (c) 2025-2026, s0up and the autobrr contributors. | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package linkdir | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
|
|
||
| "github.com/autobrr/qui/pkg/fsutil" | ||
| "github.com/autobrr/qui/pkg/hardlinktree" | ||
| "github.com/autobrr/qui/pkg/pathutil" | ||
| ) | ||
|
|
||
| func validateInstanceDirName(name string) error { | ||
| switch trimmed := strings.TrimSpace(name); { | ||
| case trimmed == "": | ||
| return errors.New("instance directory name cannot be empty") | ||
| case strings.ContainsAny(trimmed, `/\`): | ||
| return fmt.Errorf("instance directory name %q must not contain path separators", trimmed) | ||
| case trimmed == "." || trimmed == "..": | ||
| return fmt.Errorf("instance directory name %q must not be a traversal segment", trimmed) | ||
| default: | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| func groupDestDir(baseDir, groupName, isolationFolder string) string { | ||
| groupDir := filepath.Join(baseDir, pathutil.SanitizePathSegment(groupName)) | ||
| if isolationFolder == "" { | ||
| return groupDir | ||
| } | ||
| return filepath.Join(groupDir, isolationFolder) | ||
| } | ||
|
|
||
| // EffectiveInstanceDirName returns the configured by-instance directory name. | ||
| // Falls back to the instance name when no override is set. | ||
| func EffectiveInstanceDirName(instanceName, override string) (string, error) { | ||
| name := strings.TrimSpace(override) | ||
| if name == "" { | ||
| name = instanceName | ||
| } | ||
| if err := validateInstanceDirName(name); err != nil { | ||
| return "", err | ||
| } | ||
| return name, nil | ||
| } | ||
|
|
||
| // FindMatchingBaseDir returns the first configured base dir on the same filesystem as sourcePath. | ||
| func FindMatchingBaseDir(configuredDirs, sourcePath string) (string, error) { | ||
| if strings.TrimSpace(configuredDirs) == "" { | ||
| return "", errors.New("base directory not configured") | ||
| } | ||
|
|
||
| dirs := strings.Split(configuredDirs, ",") | ||
| var lastErr error | ||
|
|
||
| for _, dir := range dirs { | ||
| dir = strings.TrimSpace(dir) | ||
| if dir == "" { | ||
| continue | ||
| } | ||
|
|
||
| if err := os.MkdirAll(dir, 0o755); err != nil { | ||
| lastErr = fmt.Errorf("failed to create directory %s: %w", dir, err) | ||
| continue | ||
| } | ||
|
|
||
| sameFS, err := fsutil.SameFilesystem(sourcePath, dir) | ||
| if err != nil { | ||
| lastErr = fmt.Errorf("failed to check filesystem for %s: %w", dir, err) | ||
| continue | ||
| } | ||
| if sameFS { | ||
| return dir, nil | ||
| } | ||
|
|
||
| lastErr = fmt.Errorf("directory %s is on a different filesystem", dir) | ||
| } | ||
|
|
||
| if lastErr == nil { | ||
| lastErr = errors.New("no valid base directories configured") | ||
| } | ||
| return "", lastErr | ||
| } | ||
|
|
||
| // BuildDestDir returns the final hardlink/reflink tree root for the configured preset. | ||
| func BuildDestDir(baseDir, preset, groupName, torrentHash, torrentName string, candidateFiles []hardlinktree.TorrentFile) (string, error) { | ||
| needsIsolation := !hardlinktree.HasCommonRootFolder(candidateFiles) | ||
| isolationFolder := "" | ||
| if needsIsolation || preset == "flat" || preset == "" { | ||
| isolationFolder = pathutil.IsolationFolderName(torrentHash, torrentName) | ||
| } | ||
|
|
||
| switch preset { | ||
| case "by-tracker": | ||
| if strings.TrimSpace(groupName) == "" { | ||
| groupName = "Unknown" | ||
| } | ||
| return groupDestDir(baseDir, groupName, isolationFolder), nil | ||
| case "by-instance": | ||
| if err := validateInstanceDirName(groupName); err != nil { | ||
| return "", err | ||
| } | ||
| return groupDestDir(baseDir, groupName, isolationFolder), nil | ||
| default: | ||
| return filepath.Join(baseDir, pathutil.IsolationFolderName(torrentHash, torrentName)), nil | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.