Skip to content

Commit

Permalink
refactor: add .GetStream() to abstraction and enable nullable
Browse files Browse the repository at this point in the history
- Added `Stream? IVideo.GetStream()` and `Stream? IVideoFile.GetStream()` to the plugin abstraction, so plugins can more easily get a file stream to use through the abstraction instead of needing to rely on the `System.IO` in their code. We'll handle the lifting for you.

- Enabled nullable on the `SVR_VideoLocal_Place` file, marking any fields that should be nullable as nullable and fixing the nullable warnings that popped up as a result of this change. Also removed some left-over fields from older code that is no longer used (0 references).
  • Loading branch information
revam committed Oct 6, 2024
1 parent ae71d35 commit 930c53b
Show file tree
Hide file tree
Showing 12 changed files with 85 additions and 40 deletions.
6 changes: 6 additions & 0 deletions Shoko.Plugin.Abstractions/DataModels/IVideo.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

using System.Collections.Generic;
using System.IO;
using Shoko.Plugin.Abstractions.DataModels.Shoko;

namespace Shoko.Plugin.Abstractions.DataModels;
Expand Down Expand Up @@ -58,4 +59,9 @@ public interface IVideo : IMetadata<int>
/// Information about the group
/// </summary>
IReadOnlyList<IShokoGroup> Groups { get; }

/// <summary>
/// Get the stream for the video, if any files are still available.
/// </summary>
Stream? GetStream();
}
7 changes: 7 additions & 0 deletions Shoko.Plugin.Abstractions/DataModels/IVideoFile.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.IO;

namespace Shoko.Plugin.Abstractions.DataModels;

/// <summary>
Expand Down Expand Up @@ -50,4 +52,9 @@ public interface IVideoFile
/// The import folder tied to the video file location.
/// </summary>
IImportFolder ImportFolder { get; }

/// <summary>
/// Get the stream for the video file, if the file is still available.
/// </summary>
Stream? GetStream();
}
2 changes: 1 addition & 1 deletion Shoko.Plugin.Abstractions/Shoko.Plugin.Abstractions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<RepositoryUrl>https://github.com/ShokoAnime/ShokoServer</RepositoryUrl>
<PackageTags>plugins, shoko, anime, metadata, tagging</PackageTags>
<PackageReleaseNotes>Renamer Rewrite</PackageReleaseNotes>
<Version>4.0.0-beta8</Version>
<Version>4.0.0-beta9</Version>
<Configurations>Debug;Release;Benchmarks</Configurations>
<Platforms>AnyCPU;x64</Platforms>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
Expand Down
2 changes: 1 addition & 1 deletion Shoko.Server/API/v3/Models/Shoko/WebUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ public WebUISeriesFileSummary(
if (groupByCriteria.Contains(FileSummaryGroupByCriteria.FileIsDeprecated))
groupByDetails.FileIsDeprecated = anidbFile?.IsDeprecated ?? false;
if (groupByCriteria.Contains(FileSummaryGroupByCriteria.ImportFolder))
groupByDetails.ImportFolder = $"{location.ImportFolder.ImportFolderName} (ID: {location.ImportFolderID})";
groupByDetails.ImportFolder = $"{location.ImportFolder?.ImportFolderName ?? "N/A"} (ID: {location.ImportFolderID})";

// Video criteria
if (groupByCriteria.Contains(FileSummaryGroupByCriteria.VideoCodecs))
Expand Down
23 changes: 16 additions & 7 deletions Shoko.Server/Models/SVR_VideoLocal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#nullable enable
namespace Shoko.Server.Models;

public class SVR_VideoLocal : VideoLocal, IHash, IHashes, IVideo
public class SVR_VideoLocal : VideoLocal, IHashes, IVideo
{
#region DB columns

Expand Down Expand Up @@ -194,6 +194,21 @@ public bool HasAnyEmptyHashes()

DataSourceEnum IMetadata.Source => DataSourceEnum.Shoko;

Stream? IVideo.GetStream()
{
if (FirstResolvedPlace is not { } fileLocation)
return null;

var filePath = fileLocation.FullServerPath;
if (string.IsNullOrEmpty(filePath))
return null;

if (!File.Exists(filePath))
return null;

return File.OpenRead(filePath);
}

#endregion

#region IHashes Implementation
Expand All @@ -207,12 +222,6 @@ public bool HasAnyEmptyHashes()
string IHashes.SHA1 => SHA1;

#endregion

string IHash.ED2KHash
{
get => Hash;
set => Hash = value;
}
}

// This is a comparer used to sort the completeness of a video local, more complete first.
Expand Down
40 changes: 26 additions & 14 deletions Shoko.Server/Models/SVR_VideoLocal_Place.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
using System.IO;
using System;
using System.IO;
using Shoko.Models.Server;
using Shoko.Plugin.Abstractions.DataModels;
using Shoko.Server.Repositories;

#nullable enable
namespace Shoko.Server.Models;

public class SVR_VideoLocal_Place : VideoLocal_Place, IVideoFile
{
internal SVR_ImportFolder ImportFolder => RepoFactory.ImportFolder.GetByID(ImportFolderID);
internal SVR_ImportFolder? ImportFolder => RepoFactory.ImportFolder.GetByID(ImportFolderID);

public string FullServerPath
public string? FullServerPath
{
get
{
if (string.IsNullOrEmpty(ImportFolder?.ImportFolderLocation) || string.IsNullOrEmpty(FilePath))
{
var importFolderLocation = ImportFolder?.ImportFolderLocation;
if (string.IsNullOrEmpty(importFolderLocation) || string.IsNullOrEmpty(FilePath))
return null;
}

return Path.Combine(ImportFolder.ImportFolderLocation, FilePath);
return Path.Combine(importFolderLocation, FilePath);
}
}

public string FileName => Path.GetFileName(FilePath);

public SVR_VideoLocal VideoLocal => VideoLocalID == 0 ? null : RepoFactory.VideoLocal.GetByID(VideoLocalID);
public SVR_VideoLocal? VideoLocal => VideoLocalID is 0 ? null : RepoFactory.VideoLocal.GetByID(VideoLocalID);

public FileInfo GetFile()
public FileInfo? GetFile()
{
if (!File.Exists(FullServerPath))
{
Expand All @@ -42,9 +43,11 @@ public FileInfo GetFile()

int IVideoFile.VideoID => VideoLocalID;

IVideo IVideoFile.Video => VideoLocal;
IVideo IVideoFile.Video => VideoLocal
?? throw new NullReferenceException("Unable to get the associated IVideo for the IVideoFile with ID " + VideoLocal_Place_ID);

string IVideoFile.Path => FullServerPath;
string IVideoFile.Path => FullServerPath
?? throw new NullReferenceException("Unable to get the absolute path for the IVideoFile with ID " + VideoLocal_Place_ID);

string IVideoFile.RelativePath
{
Expand All @@ -60,11 +63,20 @@ string IVideoFile.RelativePath

long IVideoFile.Size => VideoLocal?.FileSize ?? 0;

IImportFolder IVideoFile.ImportFolder => ImportFolder;
IImportFolder IVideoFile.ImportFolder => ImportFolder
?? throw new NullReferenceException("Unable to get the associated IImportFolder for the IVideoFile with ID " + VideoLocal_Place_ID);

public IHashes Hashes => VideoLocal;
Stream? IVideoFile.GetStream()
{
var filePath = FullServerPath;
if (string.IsNullOrEmpty(filePath))
return null;

public IMediaInfo MediaInfo => VideoLocal?.MediaInfo;
if (!File.Exists(filePath))
return null;

return File.OpenRead(filePath);
}

#endregion
}
2 changes: 1 addition & 1 deletion Shoko.Server/Plex/TVShow/SVR_Episode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public SVR_AnimeEpisode AnimeEpisode
.GetAll()
.FirstOrDefault(location => location.FullServerPath?.EndsWith(filenameWithParent, StringComparison.OrdinalIgnoreCase) ?? false);

return file is null ? null : RepoFactory.AnimeEpisode.GetByHash(file.Hashes.ED2K).FirstOrDefault();
return file is null ? null : RepoFactory.AnimeEpisode.GetByHash(file.VideoLocal?.Hash).FirstOrDefault();

}
}
Expand Down
2 changes: 1 addition & 1 deletion Shoko.Server/Renamer/RenameFileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ private static RelocationResult UnAbstractResult(SVR_VideoLocal_Place place, Abs
Exception = result.Error.Exception,
};

var newImportFolder = shouldMove && !result.SkipMove ? result.DestinationImportFolder! : place.ImportFolder;
var newImportFolder = shouldMove && !result.SkipMove ? result.DestinationImportFolder! : place.ImportFolder!;
var newFileName = shouldRename && !result.SkipRename ? result.FileName! : place.FileName;
var newRelativeDirectory = shouldMove && !result.SkipMove ? result.Path! : Path.GetDirectoryName(place.FilePath)!;
var newRelativePath = newRelativeDirectory.Length > 0 ? Path.Combine(newRelativeDirectory, newFileName) : newFileName;
Expand Down
1 change: 1 addition & 0 deletions Shoko.Server/Repositories/Cached/AnimeEpisodeRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public SVR_AnimeEpisode GetByFilename(string name)
/// <returns></returns>
public List<SVR_AnimeEpisode> GetByHash(string hash)
{
if (string.IsNullOrEmpty(hash)) return [];
return RepoFactory.CrossRef_File_Episode.GetByHash(hash)
.Select(a => GetByAniDBEpisodeID(a.EpisodeID))
.Where(a => a != null)
Expand Down
4 changes: 2 additions & 2 deletions Shoko.Server/Scheduling/Jobs/Shoko/HashFileJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -391,13 +391,13 @@ private async Task<bool> ProcessDuplicates(SVR_VideoLocal vlocal, SVR_VideoLocal
// remove missing files
var preps = vlocal.Places.Where(a =>
{
if (vlocalplace.FullServerPath.Equals(a.FullServerPath)) return false;
if (string.Equals(a.FullServerPath, vlocalplace.FullServerPath)) return false;
if (a.FullServerPath == null) return true;
return !File.Exists(a.FullServerPath);
}).ToList();
RepoFactory.VideoLocalPlace.Delete(preps);

var dupPlace = vlocal.Places.FirstOrDefault(a => !vlocalplace.FullServerPath.Equals(a.FullServerPath));
var dupPlace = vlocal.Places.FirstOrDefault(a => !string.Equals(a.FullServerPath, vlocalplace.FullServerPath));
if (dupPlace == null) return false;

_logger.LogWarning("Found Duplicate File");
Expand Down
8 changes: 4 additions & 4 deletions Shoko.Server/Server/ShokoEventHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public void OnFileMatched(SVR_VideoLocal_Place vlp, SVR_VideoLocal vl)
.Select(a => a.AnimeGroup)
.WhereNotNull()
.ToList();
FileMatched?.Invoke(null, new(path, vlp.ImportFolder, vlp, vl, episodes, series, groups));
FileMatched?.Invoke(null, new(path, vlp.ImportFolder!, vlp, vl, episodes, series, groups));
}

public void OnFileNotMatched(SVR_VideoLocal_Place vlp, SVR_VideoLocal vl, int autoMatchAttempts, bool hasXRefs, bool isUDPBanned)
Expand All @@ -139,12 +139,12 @@ public void OnFileNotMatched(SVR_VideoLocal_Place vlp, SVR_VideoLocal vl, int au
.Select(a => a.AnimeGroup)
.WhereNotNull()
.ToList();
FileNotMatched?.Invoke(null, new(path, vlp.ImportFolder, vlp, vl, episodes, series, groups, autoMatchAttempts, hasXRefs, isUDPBanned));
FileNotMatched?.Invoke(null, new(path, vlp.ImportFolder!, vlp, vl, episodes, series, groups, autoMatchAttempts, hasXRefs, isUDPBanned));
}

public void OnFileMoved(IImportFolder oldFolder, IImportFolder newFolder, string oldPath, string newPath, SVR_VideoLocal_Place vlp)
{
var vl = vlp.VideoLocal;
var vl = vlp.VideoLocal!;
var xrefs = vl.EpisodeCrossRefs;
var episodes = xrefs
.Select(x => x.AnimeEpisode)
Expand All @@ -166,7 +166,7 @@ public void OnFileMoved(IImportFolder oldFolder, IImportFolder newFolder, string
public void OnFileRenamed(IImportFolder folder, string oldName, string newName, SVR_VideoLocal_Place vlp)
{
var path = vlp.FilePath;
var vl = vlp.VideoLocal;
var vl = vlp.VideoLocal!;
var xrefs = vl.EpisodeCrossRefs;
var episodes = xrefs
.Select(x => x.AnimeEpisode)
Expand Down
28 changes: 19 additions & 9 deletions Shoko.Server/Services/VideoLocal_PlaceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ public async Task<RelocationResult> DirectlyRelocateFile(SVR_VideoLocal_Place pl
ErrorMessage = "Invalid request object, import folder, or relative path.",
};

if (place.VideoLocal is not { } video)
{
_logger.LogWarning("Could not find the associated video for the file location: {LocationID}", place.VideoLocal_Place_ID);
return new()
{
Success = false,
ShouldRetry = false,
ErrorMessage = $"Could not find the associated video for the file location: {place.VideoLocal_Place_ID}",
};
}

// Sanitize relative path and reject paths leading to outside the import folder.
var fullPath = Path.GetFullPath(Path.Combine(request.ImportFolder.Path, request.RelativePath));
if (!fullPath.StartsWith(request.ImportFolder.Path, StringComparison.OrdinalIgnoreCase))
Expand Down Expand Up @@ -217,7 +228,7 @@ public async Task<RelocationResult> DirectlyRelocateFile(SVR_VideoLocal_Place pl
};
}

if (destVideoLocal.Hash == place.VideoLocal.Hash)
if (destVideoLocal.Hash == video.Hash)
{
_logger.LogDebug("Not moving file as it already exists at the new location, deleting source file instead: {PreviousPath} to {NextPath}", oldFullPath, newFullPath);

Expand Down Expand Up @@ -257,7 +268,7 @@ public async Task<RelocationResult> DirectlyRelocateFile(SVR_VideoLocal_Place pl
};
}

var aniDBFile = place.VideoLocal.AniDBFile;
var aniDBFile = video.AniDBFile;
if (aniDBFile is null)
{
_logger.LogWarning("The file does not have AniDB info. Not moving.");
Expand Down Expand Up @@ -361,7 +372,6 @@ public async Task<RelocationResult> DirectlyRelocateFile(SVR_VideoLocal_Place pl
if (renamed)
{
// Add a new or update an existing lookup entry.
var video = place.VideoLocal;
var existingEntries = RepoFactory.FileNameHash.GetByHash(video.Hash);
if (!existingEntries.Any(a => a.FileName.Equals(newFileName)))
{
Expand Down Expand Up @@ -791,7 +801,7 @@ public async Task RemoveRecordAndDeletePhysicalFile(SVR_VideoLocal_Place place,
}

if (deleteFolder)
RecursiveDeleteEmptyDirectories(Path.GetDirectoryName(place.FullServerPath), place.ImportFolder.ImportFolderLocation);
RecursiveDeleteEmptyDirectories(Path.GetDirectoryName(place.FullServerPath), place.ImportFolder!.ImportFolderLocation);

await RemoveRecord(place);
}
Expand Down Expand Up @@ -824,7 +834,7 @@ public async Task RemoveAndDeleteFileWithOpenTransaction(ISession session, SVR_V
return;
}

if (deleteFolders) RecursiveDeleteEmptyDirectories(Path.GetDirectoryName(place.FullServerPath), place.ImportFolder.ImportFolderLocation);
if (deleteFolders) RecursiveDeleteEmptyDirectories(Path.GetDirectoryName(place.FullServerPath), place.ImportFolder!.ImportFolderLocation);
await RemoveRecordWithOpenTransaction(session, place, seriesToUpdate, updateMyList);
// For deletion of files from Trakt, we will rely on the Daily sync
}
Expand Down Expand Up @@ -910,7 +920,7 @@ await scheduler.StartJob<DeleteFileFromMyListJob>(c =>

try
{
ShokoEventHandler.Instance.OnFileDeleted(place.ImportFolder, place, v);
ShokoEventHandler.Instance.OnFileDeleted(place.ImportFolder!, place, v);
}
catch
{
Expand Down Expand Up @@ -939,7 +949,7 @@ await scheduler.StartJob<DeleteFileFromMyListJob>(c =>
{
try
{
ShokoEventHandler.Instance.OnFileDeleted(place.ImportFolder, place, v);
ShokoEventHandler.Instance.OnFileDeleted(place.ImportFolder!, place, v);
}
catch
{
Expand Down Expand Up @@ -1005,7 +1015,7 @@ await scheduler.StartJob<DeleteFileFromMyListJob>(c =>

try
{
ShokoEventHandler.Instance.OnFileDeleted(place.ImportFolder, place, v);
ShokoEventHandler.Instance.OnFileDeleted(place.ImportFolder!, place, v);
}
catch
{
Expand All @@ -1026,7 +1036,7 @@ await scheduler.StartJob<DeleteFileFromMyListJob>(c =>
{
try
{
ShokoEventHandler.Instance.OnFileDeleted(place.ImportFolder, place, v);
ShokoEventHandler.Instance.OnFileDeleted(place.ImportFolder!, place, v);
}
catch
{
Expand Down

0 comments on commit 930c53b

Please sign in to comment.