Skip to content

Commit

Permalink
feat: allow disabling relocation within destinations
Browse files Browse the repository at this point in the history
- Allow disabling relocation of files within import folders that are marked as a drop destination but not as a drop source. In case you don't want Shoko to touch the files again after it was initially moved.
  • Loading branch information
revam committed Oct 7, 2024
1 parent a5ac6b3 commit cc9da8c
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 42 deletions.
70 changes: 49 additions & 21 deletions Shoko.Server/API/v3/Controllers/RenamerController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -435,12 +435,18 @@ public ActionResult DeleteRenamerConfig([FromRoute] string configName)
/// Preview the changes made by a provided Config
/// </summary>
/// <param name="args">A model for the arguments</param>
/// <param name="move">Whether or not to get the destination of the files. If `null`, defaults to `Settings.Import.MoveOnImport`</param>
/// <param name="rename">Whether or not to get the new name of the files. If `null`, defaults to `Settings.Import.RenameOnImport`</param>
/// <param name="move">Whether or not to get the destination of the files. If `null`, defaults to `Settings.Plugins.Renamer.MoveOnImport`</param>
/// <param name="rename">Whether or not to get the new name of the files. If `null`, defaults to `Settings.Plugins.Renamer.RenameOnImport`</param>
/// <param name="allowRelocationInsideDestination">Whether or not to allow relocation of files inside the destination. If `null`, defaults to `Settings.Plugins.Renamer.AllowRelocationInsideDestinationOnImport`</param>
/// <returns>A stream of relocate results.</returns>
[Authorize("admin")]
[HttpPost("Preview")]
public ActionResult<IEnumerable<RelocationResult>> BatchPreviewFilesByScriptID([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] BatchRelocateBody args, bool? move = null, bool? rename = null)
public ActionResult<IEnumerable<RelocationResult>> BatchPreviewFilesByScriptID(
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] BatchRelocateBody args,
bool? move = null,
bool? rename = null,
bool? allowRelocationInsideDestination = null
)
{
Shoko.Server.Models.RenamerConfig? config = null;
if (args.Config != null)
Expand All @@ -464,8 +470,13 @@ public ActionResult<IEnumerable<RelocationResult>> BatchPreviewFilesByScriptID([
if (!_renameFileService.RenamersByType.ContainsKey(config.Type))
return BadRequest("Renamer for Default Config not found");

var results = GetNewLocationsForFiles(args.FileIDs, config, move ?? settings.Plugins.Renamer.MoveOnImport,
rename ?? settings.Plugins.Renamer.RenameOnImport);
var results = GetNewLocationsForFiles(
args.FileIDs,
config,
move ?? settings.Plugins.Renamer.MoveOnImport,
rename ?? settings.Plugins.Renamer.RenameOnImport,
allowRelocationInsideDestination ?? settings.Plugins.Renamer.AllowRelocationInsideDestinationOnImport
);
return Ok(results);
}

Expand All @@ -474,12 +485,19 @@ public ActionResult<IEnumerable<RelocationResult>> BatchPreviewFilesByScriptID([
/// </summary>
/// <param name="configName">Config Name</param>
/// <param name="fileIDs">The file IDs to preview</param>
/// <param name="move">Whether or not to get the destination of the files. If `null`, defaults to `Settings.Import.MoveOnImport`</param>
/// <param name="rename">Whether or not to get the new name of the files. If `null`, defaults to `Settings.Import.RenameOnImport`</param>
/// <param name="move">Whether or not to get the destination of the files. If `null`, defaults to `Settings.Plugins.Renamer.MoveOnImport`</param>
/// <param name="rename">Whether or not to get the new name of the files. If `null`, defaults to `Settings.Plugins.Renamer.RenameOnImport`</param>
/// <param name="allowRelocationInsideDestination">Whether or not to allow relocation of files inside the destination. If `null`, defaults to `Settings.Plugins.Renamer.AllowRelocationInsideDestinationOnImport`</param>
/// <returns>A stream of relocate results.</returns>
[Authorize("admin")]
[HttpPost("Config/{configName}/Preview")]
public ActionResult<IEnumerable<RelocationResult>> BatchRelocateFilesByScriptID([FromRoute] string configName, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] IEnumerable<int> fileIDs, bool? move = null, bool? rename = null)
public ActionResult<IEnumerable<RelocationResult>> BatchRelocateFilesByScriptID(
[FromRoute] string configName,
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] IEnumerable<int> fileIDs,
bool? move = null,
bool? rename = null,
bool? allowRelocationInsideDestination = null
)
{
var config = _renamerConfigRepository.GetByName(configName);
if (config is null)
Expand All @@ -489,12 +507,17 @@ public ActionResult<IEnumerable<RelocationResult>> BatchRelocateFilesByScriptID(
return BadRequest("Renamer for Config not found");

var settings = _settingsProvider.GetSettings();
var results = GetNewLocationsForFiles(fileIDs, config, move ?? settings.Plugins.Renamer.MoveOnImport,
rename ?? settings.Plugins.Renamer.RenameOnImport);
var results = GetNewLocationsForFiles(
fileIDs,
config,
move ?? settings.Plugins.Renamer.MoveOnImport,
rename ?? settings.Plugins.Renamer.RenameOnImport,
allowRelocationInsideDestination ?? settings.Plugins.Renamer.AllowRelocationInsideDestinationOnImport
);
return Ok(results);
}

private IEnumerable<RelocationResult> GetNewLocationsForFiles(IEnumerable<int> fileIDs, Shoko.Server.Models.RenamerConfig config, bool move, bool rename)
private IEnumerable<RelocationResult> GetNewLocationsForFiles(IEnumerable<int> fileIDs, Shoko.Server.Models.RenamerConfig config, bool move, bool rename, bool allowRelocationInsideDestination)
{
foreach (var vlID in fileIDs)
{
Expand Down Expand Up @@ -527,7 +550,7 @@ private IEnumerable<RelocationResult> GetNewLocationsForFiles(IEnumerable<int> f
continue;
}

var result = _renameFileService.GetNewPath(vlp, config, move, rename);
var result = _renameFileService.GetNewPath(vlp, config, move, rename, allowRelocationInsideDestination);

yield return new RelocationResult
{
Expand Down Expand Up @@ -580,7 +603,8 @@ public async Task<ActionResult<RelocationResult>> DirectlyRelocateFileLocation([
{
ImportFolder = importFolder,
RelativePath = sanitizedRelativePath,
DeleteEmptyDirectories = body.DeleteEmptyDirectories
DeleteEmptyDirectories = body.DeleteEmptyDirectories,
AllowRelocationInsideDestination = true,
}
);
if (!result.Success)
Expand Down Expand Up @@ -612,14 +636,15 @@ public async Task<ActionResult<RelocationResult>> DirectlyRelocateFileLocation([
/// <param name="configName">Config Name</param>
/// <param name="fileIDs">The files to relocate</param>
/// <param name="deleteEmptyDirectories">Whether or not to delete empty directories</param>
/// <param name="move">Whether or not to move the files. If `null`, defaults to `Settings.Import.MoveOnImport`</param>
/// <param name="rename">Whether or not to rename the files. If `null`, defaults to `Settings.Import.RenameOnImport`</param>
/// <param name="move">Whether or not to move the files. If `null`, defaults to `Settings.Plugins.Renamer.MoveOnImport`</param>
/// <param name="rename">Whether or not to rename the files. If `null`, defaults to `Settings.Plugins.Renamer.RenameOnImport`</param>
/// <param name="allowRelocationInsideDestination">Whether or not to allow relocation of files inside the destination. If `null`, defaults to `Settings.Plugins.Renamer.AllowRelocationInsideDestinationOnImport`</param>
/// <returns>A stream of relocation results.</returns>
[Authorize("admin")]
[HttpPost("Config/{configName}/Relocate")]
public ActionResult<IAsyncEnumerable<RelocationResult>> BatchRelocateFilesByConfig([FromRoute] string configName,
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] IEnumerable<int> fileIDs, [FromQuery] bool deleteEmptyDirectories = true,
[FromQuery] bool? move = null, [FromQuery] bool? rename = null)
[FromQuery] bool? move = null, [FromQuery] bool? rename = null, [FromQuery] bool? allowRelocationInsideDestination = null)
{
var config = _renamerConfigRepository.GetByName(configName);
if (config is null)
Expand All @@ -635,7 +660,8 @@ public ActionResult<IAsyncEnumerable<RelocationResult>> BatchRelocateFilesByConf
Renamer = config,
DeleteEmptyDirectories = deleteEmptyDirectories,
Move = move ?? settings.Plugins.Renamer.MoveOnImport,
Rename = rename ?? settings.Plugins.Renamer.RenameOnImport
Rename = rename ?? settings.Plugins.Renamer.RenameOnImport,
AllowRelocationInsideDestination = allowRelocationInsideDestination ?? settings.Plugins.Renamer.AllowRelocationInsideDestinationOnImport,
})
);
}
Expand All @@ -645,12 +671,13 @@ public ActionResult<IAsyncEnumerable<RelocationResult>> BatchRelocateFilesByConf
/// </summary>
/// <param name="fileIDs">The files to relocate</param>
/// <param name="deleteEmptyDirectories">Whether or not to delete empty directories</param>
/// <param name="move">Whether or not to move the files. If `null`, defaults to `Settings.Import.MoveOnImport`</param>
/// <param name="rename">Whether or not to rename the files. If `null`, defaults to `Settings.Import.RenameOnImport`</param>
/// <param name="move">Whether or not to move the files. If `null`, defaults to `Settings.Plugins.Renamer.MoveOnImport`</param>
/// <param name="rename">Whether or not to rename the files. If `null`, defaults to `Settings.Plugins.Renamer.RenameOnImport`</param>
/// <param name="allowRelocationInsideDestination">Whether or not to allow relocation of files inside the destination. If `null`, defaults to `Settings.Plugins.Renamer.AllowRelocationInsideDestination`</param>
/// <returns>A stream of relocation results.</returns>
[Authorize("admin")]
[HttpPost("Relocate")]
public ActionResult<IAsyncEnumerable<RelocationResult>> BatchRelocateFilesWithDefaultConfig([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] IEnumerable<int> fileIDs, [FromQuery] bool deleteEmptyDirectories = true, [FromQuery] bool? move = null, [FromQuery] bool? rename = null)
public ActionResult<IAsyncEnumerable<RelocationResult>> BatchRelocateFilesWithDefaultConfig([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] IEnumerable<int> fileIDs, [FromQuery] bool deleteEmptyDirectories = true, [FromQuery] bool? move = null, [FromQuery] bool? rename = null, [FromQuery] bool? allowRelocationInsideDestination = null)
{
var settings = _settingsProvider.GetSettings();
var configName = settings.Plugins.Renamer.DefaultRenamer;
Expand All @@ -669,7 +696,8 @@ public ActionResult<IAsyncEnumerable<RelocationResult>> BatchRelocateFilesWithDe
Renamer = config,
DeleteEmptyDirectories = deleteEmptyDirectories,
Move = move ?? settings.Plugins.Renamer.MoveOnImport,
Rename = rename ?? settings.Plugins.Renamer.RenameOnImport
Rename = rename ?? settings.Plugins.Renamer.RenameOnImport,
AllowRelocationInsideDestination = allowRelocationInsideDestination ?? settings.Plugins.Renamer.AllowRelocationInsideDestinationOnImport,
})
);
}
Expand Down
6 changes: 6 additions & 0 deletions Shoko.Server/Renamer/AutoRelocateRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public record AutoRelocateRequest
/// </summary>
public bool DeleteEmptyDirectories { get; set; } = true;

/// <summary>
/// Indicates that we can relocate a video file that lives inside a
/// drop destination import folder that's not also a drop source.
/// </summary>
public bool AllowRelocationInsideDestination { get; set; } = true;

/// <summary>
/// Do the move operation.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions Shoko.Server/Renamer/DirectRelocationRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,10 @@ public record DirectRelocateRequest
/// relocating the file.
/// </summary>
public bool DeleteEmptyDirectories = true;

/// <summary>
/// Indicates that we can relocate a video file that lives inside a
/// drop destination import folder that's not also a drop source.
/// </summary>
public bool AllowRelocationInsideDestination { get; set; } = true;
}
18 changes: 14 additions & 4 deletions Shoko.Server/Renamer/RenameFileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,15 @@ public RenameFileService(ILogger<RenameFileService> logger, ISettingsProvider se
LoadRenamers(AppDomain.CurrentDomain.GetAssemblies());
}

public RelocationResult GetNewPath(SVR_VideoLocal_Place place, RenamerConfig? renamerConfig = null, bool? move = null, bool? rename = null)
public RelocationResult GetNewPath(SVR_VideoLocal_Place place, RenamerConfig? renamerConfig = null, bool? move = null, bool? rename = null, bool? allowRelocationInsideDestination = null)
{
var settings = _settingsProvider.GetSettings();
var shouldMove = move ?? settings.Plugins.Renamer.MoveOnImport;
var shouldRename = rename ?? settings.Plugins.Renamer.RenameOnImport;
var shouldAllowRelocationInsideDestination = allowRelocationInsideDestination ?? settings.Plugins.Renamer.AllowRelocationInsideDestinationOnImport;

// Make sure the import folder is reachable.
var importFolder = place.ImportFolder;
var importFolder = (IImportFolder?)place.ImportFolder;
if (importFolder is null)
return new()
{
Expand All @@ -57,15 +58,24 @@ public RelocationResult GetNewPath(SVR_VideoLocal_Place place, RenamerConfig? re
ErrorMessage = $"Unable to find import folder for file with ID {place.VideoLocal}.",
};

// If we try to run on a file that's not in a drop source or destination, then fail.
if (importFolder.IsDropSource == 0 && importFolder.IsDropDestination == 0)
// Don't relocate files not in a drop source or drop destination.
if (importFolder.DropFolderType is DropFolderType.Excluded)
return new()
{
Success = false,
ShouldRetry = false,
ErrorMessage = "Not relocating file as it is not in a drop source or drop destination.",
};

// Or if it's in a drop destination not also marked as a drop source and relocating inside destinations is disabled.
if (importFolder.DropFolderType is DropFolderType.Destination && !shouldAllowRelocationInsideDestination)
return new()
{
Success = false,
ShouldRetry = false,
ErrorMessage = "Not relocating file because it's in a drop destination not also marked as a drop source and relocating inside destinations is disabled.",
};

var videoLocal = place.VideoLocal ??
throw new NullReferenceException(nameof(place.VideoLocal));
var xrefs = videoLocal.EpisodeCrossRefs;
Expand Down
Loading

0 comments on commit cc9da8c

Please sign in to comment.