diff --git a/Dependencies/x64/librhash.dll b/Dependencies/x64/librhash.dll new file mode 100644 index 000000000..a1de5b32e Binary files /dev/null and b/Dependencies/x64/librhash.dll differ diff --git a/Dependencies/x86/librhash.dll b/Dependencies/x86/librhash.dll new file mode 100644 index 000000000..ded0ca61c Binary files /dev/null and b/Dependencies/x86/librhash.dll differ diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 000000000..d18772108 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM mono:5.2 + +#MAINTAINER Cayde Dixon + +RUN curl https://bintray.com/user/downloadSubjectPublicKey?username=bintray | apt-key add - +RUN echo "deb http://dl.bintray.com/cazzar/shoko-deps jesse main" | tee -a /etc/apt/sources.list + +RUN apt-get update && apt-get install -y --force-yes libmediainfo0 librhash0 sqlite.interop + +RUN mkdir -p /usr/src/app/source /usr/src/app/build +COPY . /usr/src/app/source +WORKDIR /usr/src/app/source + +ADD https://github.com/NuGet/Home/releases/download/3.3/NuGet.exe . +RUN mono NuGet.exe restore +RUN xbuild /property:Configuration=CLI /property:OutDir=/usr/src/app/build/ +RUN rm -rf /usr/src/app/source +RUN rm /usr/src/app/build/System.Net.Http.dll +WORKDIR /usr/src/app/build + +VOLUME /root/.shoko/ +VOLUME /usr/src/app/build/webui + +EXPOSE 8111 +ENTRYPOINT mono Shoko.CLI.exe diff --git a/Installers/Fix_ShokoServer_Permissions.bat b/Installers/FixPermissions.bat similarity index 100% rename from Installers/Fix_ShokoServer_Permissions.bat rename to Installers/FixPermissions.bat diff --git a/Installers/ShokoServer.iss b/Installers/ShokoServer.iss index 62145a1b2..a5e195930 100644 --- a/Installers/ShokoServer.iss +++ b/Installers/ShokoServer.iss @@ -13,7 +13,7 @@ ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) AppId={{0BA2D22B-A0B7-48F8-8AA1-BAAEFC2034CB} AppName=Shoko Server -AppVersion=3.8.0.0 +AppVersion=3.8.1.0 AppVerName=Shoko Server AppPublisher=Shoko Team AppPublisherURL=https://shokoanime.com/ @@ -35,6 +35,9 @@ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{ Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1 [Files] +Source: "..\Shoko.Server\bin\Release\System.Security.Cryptography.Algorithms.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\Shoko.Server\bin\Release\System.Security.Cryptography.Encoding.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\Shoko.Server\bin\Release\System.Security.Cryptography.Primitives.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\Shoko.Server\bin\Release\System.Security.Cryptography.X509Certificates.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\Shoko.Server\bin\Release\System.ValueTuple.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\Shoko.Server\bin\Release\System.ValueTuple.xml"; DestDir: "{app}"; Flags: ignoreversion @@ -134,9 +137,6 @@ Source: "..\Shoko.Server\bin\Release\System.Data.SQLite.xml"; DestDir: "{app}"; Source: "..\Shoko.Server\bin\Release\System.Net.Http.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\Shoko.Server\bin\Release\System.Net.Http.Formatting.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\Shoko.Server\bin\Release\System.Net.Http.Formatting.xml"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\Shoko.Server\bin\Release\System.Security.Cryptography.Algorithms.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\Shoko.Server\bin\Release\System.Security.Cryptography.Encoding.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\Shoko.Server\bin\Release\System.Security.Cryptography.Primitives.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\Shoko.Server\bin\Release\de\*"; DestDir: "{app}\de"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "..\Shoko.Server\bin\Release\en-gb\*"; DestDir: "{app}\en-gb"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "..\Shoko.Server\bin\Release\es\*"; DestDir: "{app}\es"; Flags: ignoreversion recursesubdirs createallsubdirs @@ -149,6 +149,7 @@ Source: "..\Shoko.Server\bin\Release\ru\*"; DestDir: "{app}\ru"; Flags: ignoreve Source: "..\Shoko.Server\bin\Release\webui\*"; DestDir: "{app}\webui"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "..\Shoko.Server\bin\Release\x86\*"; DestDir: "{app}\x86"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "..\Shoko.Server\bin\Release\x64\*"; DestDir: "{app}\x64"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "FixPermissions.bat"; DestDir: "{app}"; Flags: ignoreversion ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Icons] @@ -164,8 +165,9 @@ Filename: "{sys}\netsh.exe"; Parameters: "http add urlacl url=http://+:8111/JMMS Filename: "{sys}\netsh.exe"; Parameters: "http add urlacl url=http://+:8111/JMMServerStreaming sddl=D:(A;;GA;;;S-1-1-0)"; Flags: runhidden; Filename: "{sys}\netsh.exe"; Parameters: "http add urlacl url=http://+:8111/JMMServerImage sddl=D:(A;;GA;;;S-1-1-0)"; Flags: runhidden; Filename: "{sys}\netsh.exe"; Parameters: "http add urlacl url=http://+:8111/ sddl=D:(A;;GA;;;S-1-1-0)"; Flags: runhidden; +Filename: "{app}\FixPermissions.bat"; Filename: "{app}\ShokoServer.exe"; Flags: nowait postinstall skipifsilent shellexec; Description: "{cm:LaunchProgram,Shoko Server}" -Filename: "https://shokoanime.com/shoko-version-3-8-released/"; Flags: shellexec runasoriginaluser postinstall; Description: "View 3.8 Release Notes" +Filename: "https://shokoanime.com/shoko-version-3-8-1-released/"; Flags: shellexec runasoriginaluser postinstall; Description: "View 3.8.1 Release Notes" [UninstallRun] Filename: "{sys}\netsh.exe"; Parameters: "advfirewall firewall delete rule name=""Shoko Server - Client Port"" protocol=TCP localport=8111"; Flags: runhidden; StatusMsg: "Closing exception on firewall..."; Tasks: Firewall diff --git a/SharedAssemblyInfo.cs b/SharedAssemblyInfo.cs index 97611dc3c..fac05711b 100644 --- a/SharedAssemblyInfo.cs +++ b/SharedAssemblyInfo.cs @@ -4,9 +4,9 @@ using System.Runtime.InteropServices; using System.Windows; -[assembly: AssemblyVersion("3.8.0.0")] -[assembly: AssemblyTitle("Shoko Server")] +[assembly: AssemblyVersion("3.8.1.*")] [assembly: AssemblyProduct("Shoko Server")] [assembly: AssemblyCopyright("Copyright © 2017")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] \ No newline at end of file +[assembly: AssemblyCulture("")] +[assembly: AssemblyInformationalVersion("release")] \ No newline at end of file diff --git a/Shoko.CLI/App.config b/Shoko.CLI/App.config new file mode 100644 index 000000000..4d565cf29 --- /dev/null +++ b/Shoko.CLI/App.config @@ -0,0 +1,128 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Shoko.CLI/Program.cs b/Shoko.CLI/Program.cs new file mode 100644 index 000000000..fad5417dc --- /dev/null +++ b/Shoko.CLI/Program.cs @@ -0,0 +1,50 @@ +using Shoko.Server; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Shoko.Server.Commands; + +namespace Shoko.CLI +{ + class Program + { + static void Main(string[] args) + { + for (int x = 0; x < args.Length; x++) + { + if (args[x].Equals("instance", StringComparison.InvariantCultureIgnoreCase)) + { + if (x + 1 < args.Length) + { + ServerSettings.DefaultInstance = args[x + 1]; + } + } + } + + ServerSettings.LoadSettings(); + ServerState.Instance.LoadSettings(); + + ShokoServer.Instance.StartUpServer(); + + ShokoServer.RunWorkSetupDB(); + + bool running = true; + + ShokoServer.Instance.ServerShutdown += (sender, eventArgs) => running = false; + Utils.YesNoRequired += (sender, e) => + { + e.Cancel = true; + }; + + ShokoService.CmdProcessorGeneral.OnQueueStateChangedEvent += + ev => Console.WriteLine($"Queue state change: {ev.QueueState.formatMessage()}"); + + while (running) + { + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(60)); + } + } + } +} diff --git a/Shoko.CLI/Properties/AssemblyInfo.cs b/Shoko.CLI/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..28ede40a3 --- /dev/null +++ b/Shoko.CLI/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Shoko.CLI")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3a8e0177-9701-4a59-a6cd-16c6908839ea")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] diff --git a/Shoko.CLI/Shoko.CLI.csproj b/Shoko.CLI/Shoko.CLI.csproj new file mode 100644 index 000000000..5bc1f78d5 --- /dev/null +++ b/Shoko.CLI/Shoko.CLI.csproj @@ -0,0 +1,75 @@ + + + + + Debug + AnyCPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA} + Exe + Shoko.CLI + Shoko.CLI + v4.6.2 + 512 + true + + + + AnyCPU + true + full + false + ..\Shoko.Server\bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + ..\Shoko.Server\bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + Designer + + + + + {e0399140-9902-4f38-909d-85e42717ec30} + Shoko.Commons + + + {f73f9df6-580a-4341-b516-8717626f9d42} + Shoko.Models + + + {da8f0783-0f82-4106-9860-6f09ba2ea522} + Shoko.Server + + + + + + + \ No newline at end of file diff --git a/Shoko.Commons b/Shoko.Commons index 71ef4e076..7d4d326c4 160000 --- a/Shoko.Commons +++ b/Shoko.Commons @@ -1 +1 @@ -Subproject commit 71ef4e076be0ae17b3e3280b5994bfa19bb687e7 +Subproject commit 7d4d326c41971f91e163da61e8db148b63eb8441 diff --git a/Shoko.Server.sln b/Shoko.Server.sln index c1470dfe9..aba4b3dc3 100644 --- a/Shoko.Server.sln +++ b/Shoko.Server.sln @@ -1,241 +1,345 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26430.6 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shoko.Server", "Shoko.Server\Shoko.Server.csproj", "{DA8F0783-0F82-4106-9860-6F09BA2EA522}" - ProjectSection(ProjectDependencies) = postProject - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C} = {4AB1249D-D635-48A3-8F82-FAB34B69AE4C} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Hasher", "hasher\Hasher.vcxproj", "{4AB1249D-D635-48A3-8F82-FAB34B69AE4C}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CloudFilesystem", "CloudFilesystem", "{6568B082-7BB9-4D24-921A-840E8CF1612F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NutzCode.CloudFileSystem", "CloudFileSystem\NutzCode.CloudFileSystem\NutzCode.CloudFileSystem.csproj", "{29861D1A-968C-49CA-A637-88B391AA5063}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NutzCode.CloudFileSystem.Plugins.AmazonCloudDrive", "CloudFileSystem\NutzCode.CloudFileSystem.Plugins.AmazonCloudDrive\NutzCode.CloudFileSystem.Plugins.AmazonCloudDrive.csproj", "{D202B61E-A658-4208-83FC-745E04F43BDE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NutzCode.CloudFileSystem.Plugins.GoogleDrive", "CloudFileSystem\NutzCode.CloudFileSystem.Plugins.GoogleDrive\NutzCode.CloudFileSystem.Plugins.GoogleDrive.csproj", "{964A62E6-2DAE-4723-926D-D3E9597B5213}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NutzCode.CloudFileSystem.Plugins.LocalFileSystem", "CloudFileSystem\NutzCode.CloudFileSystem.Plugins.LocalFileSystem\NutzCode.CloudFileSystem.Plugins.LocalFileSystem.csproj", "{2A705C96-21D4-4061-876F-1BB954E39D25}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NutzCode.Libraries.Web", "CloudFileSystem\NutzCode.Libraries.Web\NutzCode.Libraries.Web.csproj", "{865C8B13-EB43-439E-9D69-CE7B54DCA4FF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NutzCode.CloudFileSystem.Plugins.OneDrive", "CloudFileSystem\NutzCode.CloudFileSystem.Plugins.OneDrive\NutzCode.CloudFileSystem.Plugins.OneDrive.csproj", "{D24D15AA-3E71-45A5-B2AF-7490681DEF68}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shoko.Commons", "Shoko.Commons\Shoko.Commons.csproj", "{E0399140-9902-4F38-909D-85E42717EC30}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shoko.Models", "Shoko.Commons\Shoko.Models\Shoko.Models.csproj", "{F73F9DF6-580A-4341-B516-8717626F9D42}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shoko.UI", "Shoko.UI\Shoko.UI.csproj", "{FFBA1303-E60F-4353-9ECA-434B263AF699}" - ProjectSection(ProjectDependencies) = postProject - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF} = {865C8B13-EB43-439E-9D69-CE7B54DCA4FF} - {D202B61E-A658-4208-83FC-745E04F43BDE} = {D202B61E-A658-4208-83FC-745E04F43BDE} - {2A705C96-21D4-4061-876F-1BB954E39D25} = {2A705C96-21D4-4061-876F-1BB954E39D25} - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C} = {4AB1249D-D635-48A3-8F82-FAB34B69AE4C} - {D24D15AA-3E71-45A5-B2AF-7490681DEF68} = {D24D15AA-3E71-45A5-B2AF-7490681DEF68} - {964A62E6-2DAE-4723-926D-D3E9597B5213} = {964A62E6-2DAE-4723-926D-D3E9597B5213} - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{64D264D0-B20E-4889-B535-EFDC52F921C0}" - ProjectSection(SolutionItems) = preProject - SharedAssemblyInfo.cs = SharedAssemblyInfo.cs - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|Mixed Platforms = Debug|Mixed Platforms - Debug|Win32 = Debug|Win32 - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|Mixed Platforms = Release|Mixed Platforms - Release|Win32 = Release|Win32 - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Debug|Win32.ActiveCfg = Debug|Any CPU - {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Debug|x64.ActiveCfg = Debug|Any CPU - {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Release|Any CPU.Build.0 = Release|Any CPU - {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Release|Win32.ActiveCfg = Release|Any CPU - {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Release|x64.ActiveCfg = Release|Any CPU - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C}.Debug|Any CPU.Build.0 = Debug|Win32 - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C}.Debug|Mixed Platforms.Build.0 = Debug|Win32 - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C}.Debug|Win32.ActiveCfg = Debug|Win32 - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C}.Debug|x64.ActiveCfg = Release|x64 - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C}.Debug|x64.Build.0 = Release|x64 - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C}.Release|Any CPU.ActiveCfg = Release|Win32 - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C}.Release|Any CPU.Build.0 = Release|Win32 - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C}.Release|Mixed Platforms.ActiveCfg = Release|Win32 - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C}.Release|Mixed Platforms.Build.0 = Release|Win32 - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C}.Release|Win32.ActiveCfg = Release|Win32 - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C}.Release|Win32.Build.0 = Release|Win32 - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C}.Release|x64.ActiveCfg = Release|x64 - {4AB1249D-D635-48A3-8F82-FAB34B69AE4C}.Release|x64.Build.0 = Release|x64 - {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|Win32.ActiveCfg = Debug|Any CPU - {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|Win32.Build.0 = Debug|Any CPU - {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|x64.ActiveCfg = Debug|Any CPU - {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|x64.Build.0 = Debug|Any CPU - {29861D1A-968C-49CA-A637-88B391AA5063}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29861D1A-968C-49CA-A637-88B391AA5063}.Release|Any CPU.Build.0 = Release|Any CPU - {29861D1A-968C-49CA-A637-88B391AA5063}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {29861D1A-968C-49CA-A637-88B391AA5063}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {29861D1A-968C-49CA-A637-88B391AA5063}.Release|Win32.ActiveCfg = Release|Any CPU - {29861D1A-968C-49CA-A637-88B391AA5063}.Release|Win32.Build.0 = Release|Any CPU - {29861D1A-968C-49CA-A637-88B391AA5063}.Release|x64.ActiveCfg = Release|Any CPU - {29861D1A-968C-49CA-A637-88B391AA5063}.Release|x64.Build.0 = Release|Any CPU - {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|Win32.ActiveCfg = Debug|x86 - {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|Win32.Build.0 = Debug|x86 - {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|x64.ActiveCfg = Debug|x64 - {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|x64.Build.0 = Debug|x64 - {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|Any CPU.Build.0 = Release|Any CPU - {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|Win32.ActiveCfg = Release|x86 - {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|Win32.Build.0 = Release|x86 - {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|x64.ActiveCfg = Release|x64 - {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|x64.Build.0 = Release|x64 - {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|Any CPU.Build.0 = Debug|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|Win32.ActiveCfg = Debug|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|Win32.Build.0 = Debug|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|x64.ActiveCfg = Debug|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|x64.Build.0 = Debug|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|Any CPU.ActiveCfg = Release|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|Any CPU.Build.0 = Release|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|Win32.ActiveCfg = Release|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|Win32.Build.0 = Release|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|x64.ActiveCfg = Release|Any CPU - {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|x64.Build.0 = Release|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|Win32.ActiveCfg = Debug|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|Win32.Build.0 = Debug|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|x64.ActiveCfg = Debug|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|x64.Build.0 = Debug|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|Any CPU.Build.0 = Release|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|Win32.ActiveCfg = Release|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|Win32.Build.0 = Release|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|x64.ActiveCfg = Release|Any CPU - {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|x64.Build.0 = Release|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|Win32.ActiveCfg = Debug|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|Win32.Build.0 = Debug|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|x64.ActiveCfg = Debug|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|x64.Build.0 = Debug|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|Any CPU.Build.0 = Release|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|Win32.ActiveCfg = Release|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|Win32.Build.0 = Release|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|x64.ActiveCfg = Release|Any CPU - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|x64.Build.0 = Release|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|Win32.ActiveCfg = Debug|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|Win32.Build.0 = Debug|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|x64.ActiveCfg = Debug|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|x64.Build.0 = Debug|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|Any CPU.Build.0 = Release|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|Win32.ActiveCfg = Release|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|Win32.Build.0 = Release|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|x64.ActiveCfg = Release|Any CPU - {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|x64.Build.0 = Release|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Debug|Win32.ActiveCfg = Debug|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Debug|Win32.Build.0 = Debug|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Debug|x64.ActiveCfg = Debug|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Debug|x64.Build.0 = Debug|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Release|Any CPU.Build.0 = Release|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Release|Win32.ActiveCfg = Release|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Release|Win32.Build.0 = Release|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Release|x64.ActiveCfg = Release|Any CPU - {E0399140-9902-4F38-909D-85E42717EC30}.Release|x64.Build.0 = Release|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|Win32.ActiveCfg = Debug|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|Win32.Build.0 = Debug|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|x64.ActiveCfg = Debug|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|x64.Build.0 = Debug|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|Any CPU.Build.0 = Release|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|Win32.ActiveCfg = Release|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|Win32.Build.0 = Release|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|x64.ActiveCfg = Release|Any CPU - {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|x64.Build.0 = Release|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|Win32.ActiveCfg = Debug|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|Win32.Build.0 = Debug|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|x64.ActiveCfg = Debug|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|x64.Build.0 = Debug|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|Any CPU.Build.0 = Release|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|Win32.ActiveCfg = Release|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|Win32.Build.0 = Release|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|x64.ActiveCfg = Release|Any CPU - {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|x64.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {29861D1A-968C-49CA-A637-88B391AA5063} = {6568B082-7BB9-4D24-921A-840E8CF1612F} - {D202B61E-A658-4208-83FC-745E04F43BDE} = {6568B082-7BB9-4D24-921A-840E8CF1612F} - {964A62E6-2DAE-4723-926D-D3E9597B5213} = {6568B082-7BB9-4D24-921A-840E8CF1612F} - {2A705C96-21D4-4061-876F-1BB954E39D25} = {6568B082-7BB9-4D24-921A-840E8CF1612F} - {865C8B13-EB43-439E-9D69-CE7B54DCA4FF} = {6568B082-7BB9-4D24-921A-840E8CF1612F} - {D24D15AA-3E71-45A5-B2AF-7490681DEF68} = {6568B082-7BB9-4D24-921A-840E8CF1612F} - EndGlobalSection -EndGlobal +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.16 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shoko.Server", "Shoko.Server\Shoko.Server.csproj", "{DA8F0783-0F82-4106-9860-6F09BA2EA522}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CloudFilesystem", "CloudFilesystem", "{6568B082-7BB9-4D24-921A-840E8CF1612F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NutzCode.CloudFileSystem", "CloudFileSystem\NutzCode.CloudFileSystem\NutzCode.CloudFileSystem.csproj", "{29861D1A-968C-49CA-A637-88B391AA5063}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NutzCode.CloudFileSystem.Plugins.AmazonCloudDrive", "CloudFileSystem\NutzCode.CloudFileSystem.Plugins.AmazonCloudDrive\NutzCode.CloudFileSystem.Plugins.AmazonCloudDrive.csproj", "{D202B61E-A658-4208-83FC-745E04F43BDE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NutzCode.CloudFileSystem.Plugins.GoogleDrive", "CloudFileSystem\NutzCode.CloudFileSystem.Plugins.GoogleDrive\NutzCode.CloudFileSystem.Plugins.GoogleDrive.csproj", "{964A62E6-2DAE-4723-926D-D3E9597B5213}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NutzCode.CloudFileSystem.Plugins.LocalFileSystem", "CloudFileSystem\NutzCode.CloudFileSystem.Plugins.LocalFileSystem\NutzCode.CloudFileSystem.Plugins.LocalFileSystem.csproj", "{2A705C96-21D4-4061-876F-1BB954E39D25}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NutzCode.Libraries.Web", "CloudFileSystem\NutzCode.Libraries.Web\NutzCode.Libraries.Web.csproj", "{865C8B13-EB43-439E-9D69-CE7B54DCA4FF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NutzCode.CloudFileSystem.Plugins.OneDrive", "CloudFileSystem\NutzCode.CloudFileSystem.Plugins.OneDrive\NutzCode.CloudFileSystem.Plugins.OneDrive.csproj", "{D24D15AA-3E71-45A5-B2AF-7490681DEF68}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shoko.Commons", "Shoko.Commons\Shoko.Commons.csproj", "{E0399140-9902-4F38-909D-85E42717EC30}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shoko.Models", "Shoko.Commons\Shoko.Models\Shoko.Models.csproj", "{F73F9DF6-580A-4341-B516-8717626F9D42}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shoko.UI", "Shoko.UI\Shoko.UI.csproj", "{FFBA1303-E60F-4353-9ECA-434B263AF699}" + ProjectSection(ProjectDependencies) = postProject + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF} = {865C8B13-EB43-439E-9D69-CE7B54DCA4FF} + {D202B61E-A658-4208-83FC-745E04F43BDE} = {D202B61E-A658-4208-83FC-745E04F43BDE} + {2A705C96-21D4-4061-876F-1BB954E39D25} = {2A705C96-21D4-4061-876F-1BB954E39D25} + {D24D15AA-3E71-45A5-B2AF-7490681DEF68} = {D24D15AA-3E71-45A5-B2AF-7490681DEF68} + {964A62E6-2DAE-4723-926D-D3E9597B5213} = {964A62E6-2DAE-4723-926D-D3E9597B5213} + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{64D264D0-B20E-4889-B535-EFDC52F921C0}" + ProjectSection(SolutionItems) = preProject + Dockerfile = Dockerfile + SharedAssemblyInfo.cs = SharedAssemblyInfo.cs + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shoko.CLI", "Shoko.CLI\Shoko.CLI.csproj", "{3A8E0177-9701-4A59-A6CD-16C6908839EA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{00DED27E-E274-4202-9527-4E7D07E384FC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "x86", "x86", "{700D0E2E-FF22-4810-8BC5-537207182EDF}" + ProjectSection(SolutionItems) = preProject + Dependencies\x86\librhash.dll = Dependencies\x86\librhash.dll + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "x64", "x64", "{99CC23E0-FC68-4531-8C8D-72D38CA98F06}" + ProjectSection(SolutionItems) = preProject + Dependencies\x64\librhash.dll = Dependencies\x64\librhash.dll + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + CLI|Any CPU = CLI|Any CPU + CLI|Mixed Platforms = CLI|Mixed Platforms + CLI|Win32 = CLI|Win32 + CLI|x64 = CLI|x64 + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.CLI|Any CPU.ActiveCfg = Release|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.CLI|Any CPU.Build.0 = Release|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.CLI|Mixed Platforms.ActiveCfg = Release|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.CLI|Mixed Platforms.Build.0 = Release|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.CLI|Win32.ActiveCfg = Release|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.CLI|x64.ActiveCfg = Release|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Debug|Win32.ActiveCfg = Debug|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Debug|x64.ActiveCfg = Debug|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Release|Any CPU.Build.0 = Release|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Release|Win32.ActiveCfg = Release|Any CPU + {DA8F0783-0F82-4106-9860-6F09BA2EA522}.Release|x64.ActiveCfg = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.CLI|Any CPU.ActiveCfg = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.CLI|Any CPU.Build.0 = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.CLI|Mixed Platforms.ActiveCfg = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.CLI|Mixed Platforms.Build.0 = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.CLI|Win32.ActiveCfg = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.CLI|Win32.Build.0 = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.CLI|x64.ActiveCfg = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.CLI|x64.Build.0 = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|Win32.ActiveCfg = Debug|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|Win32.Build.0 = Debug|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|x64.ActiveCfg = Debug|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Debug|x64.Build.0 = Debug|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Release|Any CPU.Build.0 = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Release|Win32.ActiveCfg = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Release|Win32.Build.0 = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Release|x64.ActiveCfg = Release|Any CPU + {29861D1A-968C-49CA-A637-88B391AA5063}.Release|x64.Build.0 = Release|Any CPU + {D202B61E-A658-4208-83FC-745E04F43BDE}.CLI|Any CPU.ActiveCfg = Release|Any CPU + {D202B61E-A658-4208-83FC-745E04F43BDE}.CLI|Any CPU.Build.0 = Release|Any CPU + {D202B61E-A658-4208-83FC-745E04F43BDE}.CLI|Mixed Platforms.ActiveCfg = Release|Any CPU + {D202B61E-A658-4208-83FC-745E04F43BDE}.CLI|Mixed Platforms.Build.0 = Release|Any CPU + {D202B61E-A658-4208-83FC-745E04F43BDE}.CLI|Win32.ActiveCfg = Release|x86 + {D202B61E-A658-4208-83FC-745E04F43BDE}.CLI|Win32.Build.0 = Release|x86 + {D202B61E-A658-4208-83FC-745E04F43BDE}.CLI|x64.ActiveCfg = Release|x64 + {D202B61E-A658-4208-83FC-745E04F43BDE}.CLI|x64.Build.0 = Release|x64 + {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|Win32.ActiveCfg = Debug|x86 + {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|Win32.Build.0 = Debug|x86 + {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|x64.ActiveCfg = Debug|x64 + {D202B61E-A658-4208-83FC-745E04F43BDE}.Debug|x64.Build.0 = Debug|x64 + {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|Any CPU.Build.0 = Release|Any CPU + {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|Win32.ActiveCfg = Release|x86 + {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|Win32.Build.0 = Release|x86 + {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|x64.ActiveCfg = Release|x64 + {D202B61E-A658-4208-83FC-745E04F43BDE}.Release|x64.Build.0 = Release|x64 + {964A62E6-2DAE-4723-926D-D3E9597B5213}.CLI|Any CPU.ActiveCfg = Release|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.CLI|Any CPU.Build.0 = Release|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.CLI|Mixed Platforms.ActiveCfg = Release|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.CLI|Mixed Platforms.Build.0 = Release|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.CLI|Win32.ActiveCfg = Release|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.CLI|Win32.Build.0 = Release|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.CLI|x64.ActiveCfg = Release|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.CLI|x64.Build.0 = Release|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|Any CPU.Build.0 = Debug|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|Win32.ActiveCfg = Debug|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|Win32.Build.0 = Debug|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|x64.ActiveCfg = Debug|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Debug|x64.Build.0 = Debug|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|Any CPU.ActiveCfg = Release|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|Any CPU.Build.0 = Release|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|Win32.ActiveCfg = Release|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|Win32.Build.0 = Release|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|x64.ActiveCfg = Release|Any CPU + {964A62E6-2DAE-4723-926D-D3E9597B5213}.Release|x64.Build.0 = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.CLI|Any CPU.ActiveCfg = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.CLI|Any CPU.Build.0 = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.CLI|Mixed Platforms.ActiveCfg = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.CLI|Mixed Platforms.Build.0 = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.CLI|Win32.ActiveCfg = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.CLI|Win32.Build.0 = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.CLI|x64.ActiveCfg = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.CLI|x64.Build.0 = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|Win32.ActiveCfg = Debug|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|Win32.Build.0 = Debug|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|x64.ActiveCfg = Debug|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Debug|x64.Build.0 = Debug|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|Any CPU.Build.0 = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|Win32.ActiveCfg = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|Win32.Build.0 = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|x64.ActiveCfg = Release|Any CPU + {2A705C96-21D4-4061-876F-1BB954E39D25}.Release|x64.Build.0 = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.CLI|Any CPU.ActiveCfg = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.CLI|Any CPU.Build.0 = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.CLI|Mixed Platforms.ActiveCfg = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.CLI|Mixed Platforms.Build.0 = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.CLI|Win32.ActiveCfg = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.CLI|Win32.Build.0 = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.CLI|x64.ActiveCfg = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.CLI|x64.Build.0 = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|Win32.ActiveCfg = Debug|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|Win32.Build.0 = Debug|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|x64.ActiveCfg = Debug|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Debug|x64.Build.0 = Debug|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|Any CPU.Build.0 = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|Win32.ActiveCfg = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|Win32.Build.0 = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|x64.ActiveCfg = Release|Any CPU + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF}.Release|x64.Build.0 = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.CLI|Any CPU.ActiveCfg = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.CLI|Any CPU.Build.0 = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.CLI|Mixed Platforms.ActiveCfg = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.CLI|Mixed Platforms.Build.0 = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.CLI|Win32.ActiveCfg = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.CLI|Win32.Build.0 = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.CLI|x64.ActiveCfg = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.CLI|x64.Build.0 = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|Win32.ActiveCfg = Debug|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|Win32.Build.0 = Debug|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|x64.ActiveCfg = Debug|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Debug|x64.Build.0 = Debug|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|Any CPU.Build.0 = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|Win32.ActiveCfg = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|Win32.Build.0 = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|x64.ActiveCfg = Release|Any CPU + {D24D15AA-3E71-45A5-B2AF-7490681DEF68}.Release|x64.Build.0 = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.CLI|Any CPU.ActiveCfg = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.CLI|Any CPU.Build.0 = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.CLI|Mixed Platforms.ActiveCfg = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.CLI|Mixed Platforms.Build.0 = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.CLI|Win32.ActiveCfg = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.CLI|Win32.Build.0 = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.CLI|x64.ActiveCfg = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.CLI|x64.Build.0 = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Debug|Win32.ActiveCfg = Debug|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Debug|Win32.Build.0 = Debug|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Debug|x64.ActiveCfg = Debug|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Debug|x64.Build.0 = Debug|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Release|Any CPU.Build.0 = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Release|Win32.ActiveCfg = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Release|Win32.Build.0 = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Release|x64.ActiveCfg = Release|Any CPU + {E0399140-9902-4F38-909D-85E42717EC30}.Release|x64.Build.0 = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.CLI|Any CPU.ActiveCfg = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.CLI|Any CPU.Build.0 = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.CLI|Mixed Platforms.ActiveCfg = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.CLI|Mixed Platforms.Build.0 = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.CLI|Win32.ActiveCfg = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.CLI|Win32.Build.0 = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.CLI|x64.ActiveCfg = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.CLI|x64.Build.0 = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|Win32.ActiveCfg = Debug|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|Win32.Build.0 = Debug|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|x64.ActiveCfg = Debug|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Debug|x64.Build.0 = Debug|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|Any CPU.Build.0 = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|Win32.ActiveCfg = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|Win32.Build.0 = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|x64.ActiveCfg = Release|Any CPU + {F73F9DF6-580A-4341-B516-8717626F9D42}.Release|x64.Build.0 = Release|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.CLI|Any CPU.ActiveCfg = Release|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.CLI|Any CPU.Build.0 = Release|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.CLI|Mixed Platforms.ActiveCfg = Release|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.CLI|Win32.ActiveCfg = Release|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.CLI|Win32.Build.0 = Release|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.CLI|x64.ActiveCfg = Release|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.CLI|x64.Build.0 = Release|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|Win32.ActiveCfg = Debug|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|Win32.Build.0 = Debug|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|x64.ActiveCfg = Debug|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Debug|x64.Build.0 = Debug|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|Any CPU.Build.0 = Release|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|Win32.ActiveCfg = Release|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|Win32.Build.0 = Release|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|x64.ActiveCfg = Release|Any CPU + {FFBA1303-E60F-4353-9ECA-434B263AF699}.Release|x64.Build.0 = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.CLI|Any CPU.ActiveCfg = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.CLI|Any CPU.Build.0 = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.CLI|Mixed Platforms.ActiveCfg = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.CLI|Mixed Platforms.Build.0 = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.CLI|Win32.ActiveCfg = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.CLI|Win32.Build.0 = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.CLI|x64.ActiveCfg = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.CLI|x64.Build.0 = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Debug|Win32.ActiveCfg = Debug|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Debug|Win32.Build.0 = Debug|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Debug|x64.ActiveCfg = Debug|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Debug|x64.Build.0 = Debug|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Release|Any CPU.Build.0 = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Release|Win32.ActiveCfg = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Release|Win32.Build.0 = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Release|x64.ActiveCfg = Release|Any CPU + {3A8E0177-9701-4A59-A6CD-16C6908839EA}.Release|x64.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {29861D1A-968C-49CA-A637-88B391AA5063} = {6568B082-7BB9-4D24-921A-840E8CF1612F} + {D202B61E-A658-4208-83FC-745E04F43BDE} = {6568B082-7BB9-4D24-921A-840E8CF1612F} + {964A62E6-2DAE-4723-926D-D3E9597B5213} = {6568B082-7BB9-4D24-921A-840E8CF1612F} + {2A705C96-21D4-4061-876F-1BB954E39D25} = {6568B082-7BB9-4D24-921A-840E8CF1612F} + {865C8B13-EB43-439E-9D69-CE7B54DCA4FF} = {6568B082-7BB9-4D24-921A-840E8CF1612F} + {D24D15AA-3E71-45A5-B2AF-7490681DEF68} = {6568B082-7BB9-4D24-921A-840E8CF1612F} + {00DED27E-E274-4202-9527-4E7D07E384FC} = {64D264D0-B20E-4889-B535-EFDC52F921C0} + {700D0E2E-FF22-4810-8BC5-537207182EDF} = {00DED27E-E274-4202-9527-4E7D07E384FC} + {99CC23E0-FC68-4531-8C8D-72D38CA98F06} = {00DED27E-E274-4202-9527-4E7D07E384FC} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1486091B-315E-44FE-8C38-3F9D6E97919B} + EndGlobalSection +EndGlobal diff --git a/Shoko.Server/API/Bootstrapper.cs b/Shoko.Server/API/Bootstrapper.cs index 44c7e2f58..8f4aaaa8a 100644 --- a/Shoko.Server/API/Bootstrapper.cs +++ b/Shoko.Server/API/Bootstrapper.cs @@ -54,7 +54,14 @@ protected override void RequestStartup(TinyIoCContainer requestContainer, IPipel if (string.IsNullOrEmpty(apiKey)) { //take out value of "apikey" from query that was pass in request and check for User - apiKey = (string) nancyContext.Request.Query.apikey.Value; + try + { + apiKey = (string) nancyContext.Request.Query.apikey.Value; + } + catch + { + // ignore + } } AuthTokens auth = RepoFactory.AuthTokens.GetByToken(apiKey); return auth != null @@ -168,7 +175,7 @@ public void Handle(HttpStatusCode statusCode, NancyContext context) { try { - var filename = Path.Combine(_rootPathProvider.GetRootPath(), @"webui\\index.html"); + var filename = Path.Combine(_rootPathProvider.GetRootPath(), @"webui", "index.html"); using (var file = File.OpenRead(filename)) { file.CopyTo(stream); diff --git a/Shoko.Server/API/v1/Implementations/ShokoServiceImplementation/ShokoServiceImplementation_Entities.cs b/Shoko.Server/API/v1/Implementations/ShokoServiceImplementation/ShokoServiceImplementation_Entities.cs old mode 100644 new mode 100755 index 57f80edca..c0399785b --- a/Shoko.Server/API/v1/Implementations/ShokoServiceImplementation/ShokoServiceImplementation_Entities.cs +++ b/Shoko.Server/API/v1/Implementations/ShokoServiceImplementation/ShokoServiceImplementation_Entities.cs @@ -2345,6 +2345,27 @@ public CL_Response CreateSeriesFromAnime(int animeID, int? ser.Populate(anime); ser.AnimeGroupID = animeGroupID.Value; RepoFactory.AnimeSeries.Save(ser, false); + + // check for TvDB associations + if (anime.Restricted == 0) + { + CommandRequest_TvDBSearchAnime cmd = new CommandRequest_TvDBSearchAnime(anime.AnimeID, forced: false); + cmd.Save(); + + // check for Trakt associations + if (ServerSettings.Trakt_IsEnabled && !string.IsNullOrEmpty(ServerSettings.Trakt_AuthToken)) + { + CommandRequest_TraktSearchAnime cmd2 = new CommandRequest_TraktSearchAnime(anime.AnimeID, forced: false); + cmd2.Save(); + } + + if (anime.AnimeType == (int) Shoko.Models.Enums.AnimeType.Movie) + { + CommandRequest_MovieDBSearchAnime cmd3 = + new CommandRequest_MovieDBSearchAnime(anime.AnimeID, false); + cmd3.Save(); + } + } } else { @@ -2362,26 +2383,13 @@ public CL_Response CreateSeriesFromAnime(int animeID, int? cmdStatus.Save(session); } + ser.UpdateStats(true, true, false); + RepoFactory.AnimeSeries.Save(ser, false, false); - ser.UpdateStats(true, true, true); - - // check for TvDB associations - CommandRequest_TvDBSearchAnime cmd = new CommandRequest_TvDBSearchAnime(anime.AnimeID, false); - cmd.Save(session); - - if (ServerSettings.Trakt_IsEnabled && !string.IsNullOrEmpty(ServerSettings.Trakt_AuthToken)) - { - // check for Trakt associations - CommandRequest_TraktSearchAnime cmd2 = - new CommandRequest_TraktSearchAnime(anime.AnimeID, false); - cmd2.Save(session); - } - - if (anime.AnimeType == (int) AnimeType.Movie) + foreach (SVR_AnimeGroup grp in ser.AllGroupsAbove) { - CommandRequest_MovieDBSearchAnime cmd3 = - new CommandRequest_MovieDBSearchAnime(anime.AnimeID, false); - cmd3.Save(session); + grp.EpisodeAddedDate = DateTime.Now; + RepoFactory.AnimeGroup.Save(grp, true, false); } response.Result = ser.GetUserContract(userID); @@ -3074,7 +3082,7 @@ public CL_GroupFilterExtended GetGroupFilterExtended(int groupFilterID, int user SVR_JMMUser user = RepoFactory.JMMUser.GetByID(userID); if (user == null) return null; - CL_GroupFilterExtended contract = gf.ToClientExtended(session, user); + CL_GroupFilterExtended contract = gf.ToClientExtended(user); return contract; } diff --git a/Shoko.Server/API/v1/Implementations/ShokoServiceImplementation/ShokoServiceImplementation_Utilities.cs b/Shoko.Server/API/v1/Implementations/ShokoServiceImplementation/ShokoServiceImplementation_Utilities.cs index 5e4995f02..0f14997e6 100644 --- a/Shoko.Server/API/v1/Implementations/ShokoServiceImplementation/ShokoServiceImplementation_Utilities.cs +++ b/Shoko.Server/API/v1/Implementations/ShokoServiceImplementation/ShokoServiceImplementation_Utilities.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text; using System.Text.RegularExpressions; using AniDBAPI; using AniDBAPI.Commands; @@ -904,24 +905,11 @@ public List GetMissingEpisodes(int userID, bool onlyMyGroups, AiringState airState = (AiringState) airingState; - Dictionary animeCache = new Dictionary(); - Dictionary> gvqCache = - new Dictionary>(); - Dictionary> gfqCache = - new Dictionary>(); - try { - int i = 0; IReadOnlyList allSeries = RepoFactory.AnimeSeries.GetAll(); foreach (SVR_AnimeSeries ser in allSeries) { - i++; - //string msg = string.Format("Updating series {0} of {1} ({2}) - {3}", i, allSeries.Count, ser.Anime.MainTitle, DateTime.Now); - //logger.Debug(msg); - - //if (ser.Anime.AnimeID != 69) continue; - int missingEps = ser.MissingEpisodeCount; if (onlyMyGroups) missingEps = ser.MissingEpisodeCountGroups; @@ -930,128 +918,55 @@ public List GetMissingEpisodes(int userID, bool onlyMyGroups, if (!finishedAiring && airState == AiringState.FinishedAiring) continue; if (finishedAiring && airState == AiringState.StillAiring) continue; - DateTime start = DateTime.Now; - TimeSpan ts = DateTime.Now - start; + if (missingEps <= 0) continue; - double totalTiming = 0; - double timingVids = 0; - double timingSeries = 0; - double timingAnime = 0; - double timingQuality = 0; - double timingEps = 0; - double timingAniEps = 0; - int epCount = 0; + SVR_AniDB_Anime anime = ser.GetAnime(); + List summ = GetGroupVideoQualitySummary(anime.AnimeID); + List summFiles = GetGroupFileSummary(anime.AnimeID); - DateTime oStart = DateTime.Now; + StringBuilder groupSummaryBuilder = new StringBuilder(); + StringBuilder groupSummarySimpleBuilder = new StringBuilder(); - if (missingEps > 0) + foreach (CL_GroupVideoQuality gvq in summ) { - // find the missing episodes - start = DateTime.Now; - List eps = ser.GetAnimeEpisodes(); - ts = DateTime.Now - start; - timingEps += ts.TotalMilliseconds; - - epCount = eps.Count; - foreach (SVR_AnimeEpisode aep in ser.GetAnimeEpisodes()) - { - if (regularEpisodesOnly && aep.EpisodeTypeEnum != EpisodeType.Episode) continue; - - AniDB_Episode aniep = aep.AniDB_Episode; - if (aniep.GetFutureDated()) continue; - - start = DateTime.Now; - List vids = aep.GetVideoLocals(); - ts = DateTime.Now - start; - timingVids += ts.TotalMilliseconds; - - if (vids.Count == 0) - { - CL_MissingEpisode cl = new CL_MissingEpisode - { - AnimeID = ser.AniDB_ID - }; - start = DateTime.Now; - cl.AnimeSeries = ser.GetUserContract(userID); - ts = DateTime.Now - start; - timingSeries += ts.TotalMilliseconds; - - SVR_AniDB_Anime anime = null; - if (animeCache.ContainsKey(ser.AniDB_ID)) - anime = animeCache[ser.AniDB_ID]; - else - { - start = DateTime.Now; - anime = ser.GetAnime(); - ts = DateTime.Now - start; - timingAnime += ts.TotalMilliseconds; - animeCache[ser.AniDB_ID] = anime; - } - - cl.AnimeTitle = anime.MainTitle; + if (groupSummaryBuilder.Length > 0) + groupSummaryBuilder.Append(" --- "); - start = DateTime.Now; - cl.GroupFileSummary = string.Empty; - List summ = null; - if (gvqCache.ContainsKey(ser.AniDB_ID)) - summ = gvqCache[ser.AniDB_ID]; - else - { - summ = GetGroupVideoQualitySummary(anime.AnimeID); - gvqCache[ser.AniDB_ID] = summ; - } - - foreach (CL_GroupVideoQuality gvq in summ) - { - if (cl.GroupFileSummary.Length > 0) - cl.GroupFileSummary += " --- "; + groupSummaryBuilder.Append( + $"{gvq.GroupNameShort} - {gvq.Resolution}/{gvq.VideoSource}/{gvq.VideoBitDepth}bit ({gvq.NormalEpisodeNumberSummary})"); + } - cl.GroupFileSummary += string.Format("{0} - {1}/{2}/{3}bit ({4})", - gvq.GroupNameShort, gvq.Resolution, - gvq.VideoSource, gvq.VideoBitDepth, gvq.NormalEpisodeNumberSummary); - } + foreach (CL_GroupFileSummary gfq in summFiles) + { + if (groupSummarySimpleBuilder.Length > 0) + groupSummarySimpleBuilder.Append(", "); - cl.GroupFileSummarySimple = string.Empty; - List summFiles = null; - if (gfqCache.ContainsKey(ser.AniDB_ID)) - summFiles = gfqCache[ser.AniDB_ID]; - else - { - summFiles = GetGroupFileSummary(anime.AnimeID); - gfqCache[ser.AniDB_ID] = summFiles; - } + groupSummarySimpleBuilder.Append($"{gfq.GroupNameShort} ({gfq.NormalEpisodeNumberSummary})"); + } - foreach (CL_GroupFileSummary gfq in summFiles) - { - if (cl.GroupFileSummarySimple.Length > 0) - cl.GroupFileSummarySimple += ", "; + // find the missing episodes + foreach (SVR_AnimeEpisode aep in ser.GetAnimeEpisodes()) + { + if (regularEpisodesOnly && aep.EpisodeTypeEnum != EpisodeType.Episode) continue; - cl.GroupFileSummarySimple += string.Format("{0} ({1})", gfq.GroupNameShort, - gfq.NormalEpisodeNumberSummary); - } + AniDB_Episode aniep = aep.AniDB_Episode; + if (aniep.GetFutureDated()) continue; - ts = DateTime.Now - start; - timingQuality += ts.TotalMilliseconds; - animeCache[ser.AniDB_ID] = anime; - - start = DateTime.Now; - cl.EpisodeID = aniep.EpisodeID; - cl.EpisodeNumber = aniep.EpisodeNumber; - cl.EpisodeType = aniep.EpisodeType; - contracts.Add(cl); - ts = DateTime.Now - start; - timingAniEps += ts.TotalMilliseconds; - } - } + List vids = aep.GetVideoLocals(); - ts = DateTime.Now - oStart; - totalTiming = ts.TotalMilliseconds; + if (vids.Count != 0) continue; - string msg2 = - string.Format("Timing for series {0} ({1}) : {2}/{3}/{4}/{5}/{6}/{7} - {8} eps (AID: {9})", - ser.GetAnime().MainTitle, totalTiming, timingVids, timingSeries, - timingAnime, timingQuality, timingEps, timingAniEps, epCount, ser.GetAnime().AnimeID); - //logger.Debug(msg2); + contracts.Add(new CL_MissingEpisode + { + AnimeID = ser.AniDB_ID, + AnimeSeries = ser.GetUserContract(userID), + AnimeTitle = anime.MainTitle, + EpisodeID = aniep.EpisodeID, + EpisodeNumber = aniep.EpisodeNumber, + EpisodeType = aniep.EpisodeType, + GroupFileSummary = groupSummaryBuilder.ToString(), + GroupFileSummarySimple = groupSummarySimpleBuilder.ToString() + }); } } contracts = contracts.OrderBy(a => a.AnimeTitle) @@ -1069,21 +984,6 @@ public List GetMissingEpisodes(int userID, bool onlyMyGroups, public List GetMyListFilesForRemoval(int userID) { List contracts = new List(); - - /*Contract_MissingFile missingFile2 = new Contract_MissingFile(); - missingFile2.AnimeID = 1; - missingFile2.AnimeTitle = "Gundam Age"; - missingFile2.EpisodeID = 2; - missingFile2.EpisodeNumber = 7; - missingFile2.FileID = 8; - missingFile2.AnimeSeries = null; - contracts.Add(missingFile2); - - Thread.Sleep(5000); - - return contracts;*/ - - Dictionary animeCache = new Dictionary(); Dictionary animeSeriesCache = new Dictionary(); @@ -1730,50 +1630,55 @@ public List GetGroupFileSummary(int animeID) bool foundSummaryRecord = false; foreach (CL_GroupFileSummary contract in vidQuals) { - if (contract.GroupName.Equals(aniFile.Anime_GroupName, - StringComparison.InvariantCultureIgnoreCase)) - { - foundSummaryRecord = true; + if (!contract.GroupName.Equals(aniFile.Anime_GroupName, + StringComparison.InvariantCultureIgnoreCase)) continue; - if (animeEp.EpisodeTypeEnum == EpisodeType.Episode) + foundSummaryRecord = true; + + switch (animeEp.EpisodeTypeEnum) + { + case EpisodeType.Episode: contract.FileCountNormal++; - if (animeEp.EpisodeTypeEnum == EpisodeType.Special) + break; + case EpisodeType.Special: contract.FileCountSpecials++; - contract.TotalFileSize += aniFile.FileSize; - contract.TotalRunningTime += aniFile.File_LengthSeconds; - - if (animeEp.EpisodeTypeEnum == EpisodeType.Episode) - { - if (!contract.NormalEpisodeNumbers.Contains(anidbEp.EpisodeNumber)) - contract.NormalEpisodeNumbers.Add(anidbEp.EpisodeNumber); - } + break; } + contract.TotalFileSize += aniFile.FileSize; + contract.TotalRunningTime += aniFile.File_LengthSeconds; + + if (animeEp.EpisodeTypeEnum != EpisodeType.Episode) continue; + if (!contract.NormalEpisodeNumbers.Contains(anidbEp.EpisodeNumber)) + contract.NormalEpisodeNumbers.Add(anidbEp.EpisodeNumber); } - if (!foundSummaryRecord) + if (foundSummaryRecord) continue; + CL_GroupFileSummary cl = new CL_GroupFileSummary { - CL_GroupFileSummary cl = new CL_GroupFileSummary - { - FileCountNormal = 0, - FileCountSpecials = 0, - TotalFileSize = 0, - TotalRunningTime = 0 - }; - if (animeEp.EpisodeTypeEnum == EpisodeType.Episode) cl.FileCountNormal++; - if (animeEp.EpisodeTypeEnum == EpisodeType.Special) cl.FileCountSpecials++; - cl.TotalFileSize += aniFile.FileSize; - cl.TotalRunningTime += aniFile.File_LengthSeconds; + FileCountNormal = 0, + FileCountSpecials = 0, + TotalFileSize = 0, + TotalRunningTime = 0 + }; + switch (animeEp.EpisodeTypeEnum) + { + case EpisodeType.Episode: + cl.FileCountNormal++; + break; + case EpisodeType.Special: + cl.FileCountSpecials++; + break; + } + cl.TotalFileSize += aniFile.FileSize; + cl.TotalRunningTime += aniFile.File_LengthSeconds; - cl.GroupName = aniFile.Anime_GroupName; - cl.GroupNameShort = aniFile.Anime_GroupNameShort; - cl.NormalEpisodeNumbers = new List(); - if (animeEp.EpisodeTypeEnum == EpisodeType.Episode) - { - if (!cl.NormalEpisodeNumbers.Contains(anidbEp.EpisodeNumber)) - cl.NormalEpisodeNumbers.Add(anidbEp.EpisodeNumber); - } + cl.GroupName = aniFile.Anime_GroupName; + cl.GroupNameShort = aniFile.Anime_GroupNameShort; + cl.NormalEpisodeNumbers = new List(); + if (animeEp.EpisodeTypeEnum == EpisodeType.Episode && + !cl.NormalEpisodeNumbers.Contains(anidbEp.EpisodeNumber)) + cl.NormalEpisodeNumbers.Add(anidbEp.EpisodeNumber); - vidQuals.Add(cl); - } + vidQuals.Add(cl); } else { diff --git a/Shoko.Server/API/v1/Implementations/ShokoServiceImplementationMetro.cs b/Shoko.Server/API/v1/Implementations/ShokoServiceImplementationMetro.cs index 9dd460699..50ab7d1d7 100644 --- a/Shoko.Server/API/v1/Implementations/ShokoServiceImplementationMetro.cs +++ b/Shoko.Server/API/v1/Implementations/ShokoServiceImplementationMetro.cs @@ -512,7 +512,7 @@ public List GetAnimeCalendar(int jmmuserID, int startDateSe DateTime? endDate = AniDB.GetAniDBDateAsDate(endDateSecs); List animes = - RepoFactory.AniDB_Anime.GetForDate(session, startDate.Value, endDate.Value); + RepoFactory.AniDB_Anime.GetForDate(startDate.Value, endDate.Value); foreach (SVR_AniDB_Anime anidb_anime in animes) { if (!user.AllowedAnime(anidb_anime)) continue; @@ -569,7 +569,7 @@ public List SearchAnime(int jmmuserID, string queryText, in if (user == null) return retAnime; - List animes = RepoFactory.AniDB_Anime.SearchByName(session, queryText); + List animes = RepoFactory.AniDB_Anime.SearchByName(queryText); foreach (SVR_AniDB_Anime anidb_anime in animes) { if (!user.AllowedAnime(anidb_anime)) continue; diff --git a/Shoko.Server/API/v2/Models/core/APIMessage.cs b/Shoko.Server/API/v2/Models/core/APIMessage.cs index 6b70232fa..b28b6b490 100644 --- a/Shoko.Server/API/v2/Models/core/APIMessage.cs +++ b/Shoko.Server/API/v2/Models/core/APIMessage.cs @@ -28,30 +28,30 @@ public APIMessage(int _code, string _message) public static class APIStatus { - public static APIMessage processing() => new APIMessage(100, "processing"); + public static APIMessage Processing() => new APIMessage(100, "processing"); - public static APIMessage processing(string custom_message) => new APIMessage(100, custom_message); + public static APIMessage Processing(string custom_message) => new APIMessage(100, custom_message); - public static APIMessage statusOK() => new APIMessage(200, "ok"); + public static APIMessage OK() => new APIMessage(200, "ok"); - public static APIMessage statusOK(string custom_message) => new APIMessage(200, custom_message); + public static APIMessage OK(string custom_message) => new APIMessage(200, custom_message); - public static APIMessage badRequest() => new APIMessage(400, "Bad Request"); + public static APIMessage BadRequest() => new APIMessage(400, "Bad Request"); - public static APIMessage badRequest(string custom_message) => new APIMessage(400, custom_message); + public static APIMessage BadRequest(string custom_message) => new APIMessage(400, custom_message); - public static APIMessage unauthorized() => new APIMessage(401, "Unauthorized"); + public static APIMessage Unauthorized() => new APIMessage(401, "Unauthorized"); - public static APIMessage adminNeeded() => new APIMessage(403, "Admin rights needed"); + public static APIMessage AdminNeeded() => new APIMessage(403, "Admin rights needed"); - public static APIMessage accessDenied() => new APIMessage(403, "Access Denied"); + public static APIMessage AccessDenied() => new APIMessage(403, "Access Denied"); - public static APIMessage notFound404(string message = "Not Found") => new APIMessage(404, message); + public static APIMessage NotFound(string message = "Not Found") => new APIMessage(404, message); - public static APIMessage internalError(string custom_message = "Internal Error") => new APIMessage(500, custom_message); + public static APIMessage InternalError(string custom_message = "Internal Error") => new APIMessage(500, custom_message); - public static APIMessage notImplemented() => new APIMessage(501, "Not Implemented"); + public static APIMessage NotImplemented() => new APIMessage(501, "Not Implemented"); - public static APIMessage serverDown() => new APIMessage(503, "Server is not Running"); + public static APIMessage ServiceUnavailable() => new APIMessage(503, "Server is not Running"); } } \ No newline at end of file diff --git a/Shoko.Server/API/v2/Models/core/Creditentials.cs b/Shoko.Server/API/v2/Models/core/Credentials.cs similarity index 71% rename from Shoko.Server/API/v2/Models/core/Creditentials.cs rename to Shoko.Server/API/v2/Models/core/Credentials.cs index 9fce50ec8..a2aab40f8 100644 --- a/Shoko.Server/API/v2/Models/core/Creditentials.cs +++ b/Shoko.Server/API/v2/Models/core/Credentials.cs @@ -1,11 +1,13 @@ namespace Shoko.Server.API.v2.Models.core { - public class Creditentials + public class Credentials { public string login { get; set; } public string password { get; set; } public int port { get; set; } public string token { get; set; } public string refresh_token { get; set; } + public string apikey { get; set; } + public int apiport { get; set; } } } \ No newline at end of file diff --git a/Shoko.Server/API/v2/Models/core/Database.cs b/Shoko.Server/API/v2/Models/core/Database.cs deleted file mode 100644 index 7fbf106fe..000000000 --- a/Shoko.Server/API/v2/Models/core/Database.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Shoko.Server.API.v2.Models.core -{ - public class Database - { - public string type { get; set; } - public string login { get; set; } - public string password { get; set; } - public string table { get; set; } - public string path { get; set; } - public string server { get; set; } - } -} \ No newline at end of file diff --git a/Shoko.Server/API/v2/Models/core/DatabaseSettings.cs b/Shoko.Server/API/v2/Models/core/DatabaseSettings.cs new file mode 100644 index 000000000..f4da7b883 --- /dev/null +++ b/Shoko.Server/API/v2/Models/core/DatabaseSettings.cs @@ -0,0 +1,26 @@ +namespace Shoko.Server.API.v2.Models.core +{ + public class DatabaseSettings + { + // case invariant, no spaces representation of db type + public string db_type { get; set; } + + public string sqlserver_databaseserver { get; set; } + + public string sqlserver_databasename { get; set; } + + public string sqlserver_username { get; set; } + + public string sqlserver_password { get; set; } + + public string sqlite_databasefile { get; set; } + + public string mysql_hostname { get; set; } + + public string mysql_schemaname { get; set; } + + public string mysql_username { get; set; } + + public string mysql_password { get; set; } + } +} \ No newline at end of file diff --git a/Shoko.Server/API/v2/Models/core/ServerStatus.cs b/Shoko.Server/API/v2/Models/core/ServerStatus.cs new file mode 100644 index 000000000..b11bf9642 --- /dev/null +++ b/Shoko.Server/API/v2/Models/core/ServerStatus.cs @@ -0,0 +1,16 @@ +namespace Shoko.Server.API.v2.Models.core +{ + public class ServerStatus + { + // The message that is usually displayed at the top of the server UI during startup + public string startup_state { get; set; } + // Is the server running + public bool server_started { get; set; } + // Is the first run setting flag marked + public bool first_run { get; set; } + // Did the server fail to start + public bool startup_failed { get; set; } + // Why did it fail + public string startup_failed_error_message { get; set; } + } +} \ No newline at end of file diff --git a/Shoko.Server/API/v2/Modules/Auth.cs b/Shoko.Server/API/v2/Modules/Auth.cs index 53f7544ff..c4bfc82cb 100644 --- a/Shoko.Server/API/v2/Modules/Auth.cs +++ b/Shoko.Server/API/v2/Modules/Auth.cs @@ -8,8 +8,6 @@ namespace Shoko.Server.API.v2.Modules { public class Auth : NancyModule { - public static int version = 1; - /// /// Authentication module /// @@ -21,22 +19,20 @@ public Auth() : base("/api/auth") { //Bind POST body AuthUser auth = this.Bind(); - if (!string.IsNullOrEmpty(auth.user)) - { - if (auth.pass == null) auth.pass = string.Empty; - if (!string.IsNullOrEmpty(auth.device) && auth.pass != null) - { - //create and save new token for authenticated user or return known one - string apiKey = RepoFactory.AuthTokens.ValidateUser(auth.user, auth.pass, auth.device); + if (string.IsNullOrEmpty(auth.user.Trim())) + return new Response {StatusCode = HttpStatusCode.BadRequest}; + + if (auth.pass == null) auth.pass = string.Empty; + //if password or device is missing + if (string.IsNullOrEmpty(auth.device) || auth.pass == null) + return new Response {StatusCode = HttpStatusCode.BadRequest}; + + //create and save new token for authenticated user or return known one + string apiKey = RepoFactory.AuthTokens.ValidateUser(auth.user.Trim(), auth.pass.Trim(), auth.device.Trim()); - if (!string.IsNullOrEmpty(apiKey)) return Response.AsJson(new {apikey = apiKey}); + if (!string.IsNullOrEmpty(apiKey)) return Response.AsJson(new {apikey = apiKey}); - return new Response { StatusCode = HttpStatusCode.Unauthorized }; - } - //if password or device is missing - return new Response { StatusCode = HttpStatusCode.BadRequest }; - } - return new Response { StatusCode = HttpStatusCode.ExpectationFailed }; + return new Response { StatusCode = HttpStatusCode.Unauthorized }; }, ct); //remove apikey from database @@ -45,7 +41,7 @@ public Auth() : base("/api/auth") { var apiKey = (string) Request.Query.apikey; RepoFactory.AuthTokens.DeleteWithToken(apiKey); - return APIStatus.statusOK(); + return APIStatus.OK(); }, ct); } } diff --git a/Shoko.Server/API/v2/Modules/Common.cs b/Shoko.Server/API/v2/Modules/Common.cs index ad2eb9f4e..85e25bacb 100644 --- a/Shoko.Server/API/v2/Modules/Common.cs +++ b/Shoko.Server/API/v2/Modules/Common.cs @@ -1,38 +1,37 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Dynamic; using System.IO; using System.Linq; +using System.Net; using System.Threading; -using AniDBAPI; +using System.Threading.Tasks; +using System.Web; using Nancy; using Nancy.ModelBinding; using Nancy.Security; using Newtonsoft.Json; -using NHibernate.Util; using NLog; using Shoko.Commons.Extensions; +using Shoko.Commons.Utils; using Shoko.Models.Client; using Shoko.Models.Enums; using Shoko.Models.Server; using Shoko.Server.API.v2.Models.common; using Shoko.Server.API.v2.Models.core; +using Shoko.Server.Commands; using Shoko.Server.Extensions; using Shoko.Server.Models; using Shoko.Server.Repositories; -using System.Threading.Tasks; -using Shoko.Commons.Utils; -using Shoko.Server.Databases; namespace Shoko.Server.API.v2.Modules { //As responds for this API we throw object that will be converted to json/xml - public class Common : Nancy.NancyModule + public class Common : NancyModule { //class will be found automagicly thanks to inherits also class need to be public (or it will 404) - public static int version = 2; - public Common() : base("/api") { // its a setting per module, so every call made to this module requires apikey @@ -65,7 +64,11 @@ public Common() : base("/api") Get["/mediainfo_update", true] = async (x,ct) => await Task.Factory.StartNew(UpdateMediaInfo, ct); Get["/hash/sync", true] = async (x,ct) => await Task.Factory.StartNew(HashSync, ct); Get["/rescan", true] = async (x,ct) => await Task.Factory.StartNew(RescanVideoLocal, ct); + Get["/rescanunlinked", true] = async (x,ct) => await Task.Factory.StartNew(RescanUnlinked, ct); + Get["/rescanmanuallinks", true] = async (x,ct) => await Task.Factory.StartNew(RescanManualLinks, ct); Get["/rehash", true] = async (x,ct) => await Task.Factory.StartNew(RehashVideoLocal, ct); + Get["/rehashunlinked", true] = async (x,ct) => await Task.Factory.StartNew(RehashUnlinked, ct); + Get["/rehashmanuallinks", true] = async (x,ct) => await Task.Factory.StartNew(RehashManualLinks, ct); #endregion @@ -211,22 +214,16 @@ private object AddFolder() if (string.IsNullOrEmpty(response.ErrorMessage)) { - return APIStatus.statusOK(); - } - else - { - return new APIMessage(500, response.ErrorMessage); + return APIStatus.OK(); } + return new APIMessage(500, response.ErrorMessage); } catch { - return APIStatus.internalError(); + return APIStatus.InternalError(); } } - else - { - return new APIMessage(400, "Bad Request: The Folder path must not be Empty"); - } + return new APIMessage(400, "Bad Request: The Folder path must not be Empty"); } catch (ModelBindingException) { @@ -250,36 +247,24 @@ private object EditFolder() { return new APIMessage(409, "The Folder Can't be both Destination and Source Simultaneously"); } - else + if (folder.ImportFolderID != 0) { - if (folder.ImportFolderID != 0) + CL_Response response = + new ShokoServiceImplementation().SaveImportFolder(folder); + if (!string.IsNullOrEmpty(response.ErrorMessage)) { - CL_Response response = - new ShokoServiceImplementation().SaveImportFolder(folder); - if (!string.IsNullOrEmpty(response.ErrorMessage)) - { - return new APIMessage(500, response.ErrorMessage); - } - else - { - return APIStatus.statusOK(); - } - } - else - { - return new APIMessage(409, "The Import Folder must have an ID"); + return new APIMessage(500, response.ErrorMessage); } + return APIStatus.OK(); } + return new APIMessage(409, "The Import Folder must have an ID"); } catch { - return APIStatus.internalError(); + return APIStatus.InternalError(); } } - else - { - return new APIMessage(400, "ImportFolderLocation and ImportFolderID missing"); - } + return new APIMessage(400, "ImportFolderLocation and ImportFolderID missing"); } /// @@ -295,17 +280,11 @@ private object DeleteFolder() string res = Importer.DeleteImportFolder(folder.ImportFolderID); if (res == string.Empty) { - return APIStatus.statusOK(); + return APIStatus.OK(); } - else - { - return new APIMessage(500, res); - } - } - else - { - return new APIMessage(400, "ImportFolderID missing"); + return new APIMessage(500, res); } + return new APIMessage(400, "ImportFolderID missing"); } /// @@ -316,7 +295,7 @@ private object DeleteFolder() private object RunImport() { ShokoServer.RunImport(); - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -327,7 +306,7 @@ private object RunImport() private object ScanDropFolders() { Importer.RunImport_DropFolders(); - return APIStatus.statusOK(); + return APIStatus.OK(); } #endregion @@ -337,19 +316,19 @@ private object ScanDropFolders() private object ListUPNP() { //TODO APIv2 ListUPNP: Need a tweak as this now should return it as list? - return APIStatus.notImplemented(); + return APIStatus.NotImplemented(); } private object AddUPNP() { //TODO APIv2 AddUPNP: implement this - return APIStatus.notImplemented(); + return APIStatus.NotImplemented(); } private object DeleteUPNP() { //TODO APIv2 DeleteUPN: implement this - return APIStatus.notImplemented(); + return APIStatus.NotImplemented(); } #endregion @@ -364,7 +343,7 @@ private object DeleteUPNP() private object RemoveMissingFiles() { ShokoServer.RemoveMissingFiles(); - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -375,7 +354,7 @@ private object RemoveMissingFiles() private object UpdateStats() { Importer.UpdateAllStats(); - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -386,7 +365,7 @@ private object UpdateStats() private object UpdateMediaInfo() { ShokoServer.RefreshAllMediaInfo(); - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -397,7 +376,7 @@ private object UpdateMediaInfo() private object HashSync() { ShokoServer.SyncHashes(); - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -407,37 +386,71 @@ private object HashSync() /// APIStatus private object RescanVideoLocal() { - Request request = this.Request; API_Call_Parameters para = this.Bind(); - if (para.id != 0) + if (para.id == 0) return APIStatus.BadRequest("missing 'id'"); + try { - try + SVR_VideoLocal vid = RepoFactory.VideoLocal.GetByID(para.id); + if (vid == null) return APIStatus.NotFound(); + if (string.IsNullOrEmpty(vid.Hash)) + return APIStatus.BadRequest("Could not Update a cloud file without hash, hash it locally first"); + CommandRequest_ProcessFile cmd = + new CommandRequest_ProcessFile(vid.VideoLocalID, true); + cmd.Save(); + return APIStatus.OK(); + } + catch (Exception ex) + { + return APIStatus.InternalError(ex.Message); + } + } + + /// + /// Handle /api/rescanunlinked + /// + /// APIStatus + private object RescanUnlinked() + { + try + { + // files which have been hashed, but don't have an associated episode + List filesWithoutEpisode = RepoFactory.VideoLocal.GetVideosWithoutEpisode(); + + foreach (SVR_VideoLocal vl in filesWithoutEpisode.Where(a => !string.IsNullOrEmpty(a.Hash))) { - SVR_VideoLocal vid = RepoFactory.VideoLocal.GetByID(para.id); - if (vid == null) - { - return APIStatus.notFound404(); - } - if (string.IsNullOrEmpty(vid.Hash)) - { - return APIStatus.badRequest( - "Could not Update a cloud file without hash, hash it locally first"); - } - Commands.CommandRequest_ProcessFile cmd = - new Commands.CommandRequest_ProcessFile(vid.VideoLocalID, true); + CommandRequest_ProcessFile cmd = new CommandRequest_ProcessFile(vl.VideoLocalID, true); cmd.Save(); } - catch (Exception ex) + return APIStatus.OK(); + } + catch (Exception ex) + { + return APIStatus.InternalError(ex.Message); + } + } + + /// + /// Handle /api/rescanmanuallinks + /// + /// APIStatus + private object RescanManualLinks() + { + try + { + // files which have been hashed, but don't have an associated episode + List filesWithoutEpisode = RepoFactory.VideoLocal.GetManuallyLinkedVideos(); + + foreach (SVR_VideoLocal vl in filesWithoutEpisode.Where(a => !string.IsNullOrEmpty(a.Hash))) { - return APIStatus.internalError(ex.Message); + CommandRequest_ProcessFile cmd = new CommandRequest_ProcessFile(vl.VideoLocalID, true); + cmd.Save(); } - - return APIStatus.statusOK(); + return APIStatus.OK(); } - else + catch (Exception ex) { - return APIStatus.badRequest("missing 'id'"); + return APIStatus.InternalError(ex.Message); } } @@ -448,34 +461,67 @@ private object RescanVideoLocal() /// APIStatus private object RehashVideoLocal() { - Request request = this.Request; API_Call_Parameters para = this.Bind(); - if (para.id != 0) + if (para.id == 0) return APIStatus.BadRequest("missing 'id'"); + SVR_VideoLocal vl = RepoFactory.VideoLocal.GetByID(para.id); + if (vl == null) return APIStatus.NotFound("VideoLocal Not Found"); + SVR_VideoLocal_Place pl = vl.GetBestVideoLocalPlace(); + if (pl?.FullServerPath == null) return APIStatus.NotFound("videolocal_place not found"); + CommandRequest_HashFile cr_hashfile = new CommandRequest_HashFile(pl.FullServerPath, true); + cr_hashfile.Save(); + + return APIStatus.OK(); + } + + /// + /// Handle /api/rehashunlinked + /// + /// APIStatus + private object RehashUnlinked() + { + try { - SVR_VideoLocal vl = RepoFactory.VideoLocal.GetByID(para.id); - if (vl != null) + // files which have been hashed, but don't have an associated episode + foreach (SVR_VideoLocal vl in RepoFactory.VideoLocal.GetVideosWithoutEpisode()) { SVR_VideoLocal_Place pl = vl.GetBestVideoLocalPlace(); - if (pl?.FullServerPath == null) - { - return APIStatus.notFound404("videolocal_place not found"); - } - Commands.CommandRequest_HashFile cr_hashfile = - new Commands.CommandRequest_HashFile(pl.FullServerPath, true); + if (pl?.FullServerPath == null) continue; + CommandRequest_HashFile cr_hashfile = new CommandRequest_HashFile(pl.FullServerPath, true); cr_hashfile.Save(); - - return APIStatus.statusOK(); } - else + } + catch (Exception ex) + { + return APIStatus.InternalError(ex.Message); + } + + return APIStatus.OK(); + } + + /// + /// Handle /api/rehashmanuallinks + /// + /// APIStatus + private object RehashManualLinks() + { + try + { + // files which have been hashed, but don't have an associated episode + foreach (SVR_VideoLocal vl in RepoFactory.VideoLocal.GetManuallyLinkedVideos()) { - return APIStatus.notFound404(); + SVR_VideoLocal_Place pl = vl.GetBestVideoLocalPlace(); + if (pl?.FullServerPath == null) continue; + CommandRequest_HashFile cr_hashfile = new CommandRequest_HashFile(pl.FullServerPath, true); + cr_hashfile.Save(); } } - else + catch (Exception ex) { - return APIStatus.badRequest("missing 'id'"); + return APIStatus.InternalError(ex.Message); } + + return APIStatus.OK(); } #endregion @@ -488,19 +534,16 @@ private object RehashVideoLocal() /// userid = int private object MyID() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; - dynamic x = new System.Dynamic.ExpandoObject(); + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; + dynamic x = new ExpandoObject(); if (user != null) { x.userid = user.JMMUserID; return x; } - else - { - x.userid = 0; - return x; - } + x.userid = 0; + return x; } /// @@ -509,7 +552,7 @@ private object MyID() /// List private object GetNews(int max) { - var client = new System.Net.WebClient(); + var client = new WebClient(); client.Headers.Add("User-Agent", "jmmserver"); client.Headers.Add("Accept", "application/json"); var response = client.DownloadString(new Uri("http://shokoanime.com/wp-json/wp/v2/posts")); @@ -524,7 +567,7 @@ private object GetNews(int max) author = post.author, date = post.date, link = post.link, - title = System.Web.HttpUtility.HtmlDecode((string)post.title.rendered), + title = HttpUtility.HtmlDecode((string)post.title.rendered), description = post.excerpt.rendered }; news.Add(wn); @@ -556,8 +599,8 @@ private object GetDashboard() /// Filter or APIStatu private object BigSearch() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); string query = para.query.ToLowerInvariant(); @@ -587,10 +630,7 @@ private object BigSearch() return search_filter; } - else - { - return APIStatus.badRequest("missing 'query'"); - } + return APIStatus.BadRequest("missing 'query'"); } /// @@ -599,8 +639,8 @@ private object BigSearch() /// Filter or APIStatu private object SearchStartsWith() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); string query = para.query.ToLowerInvariant(); @@ -630,7 +670,7 @@ private object SearchStartsWith() return search_filter; } - return APIStatus.badRequest("missing 'query'"); + return APIStatus.BadRequest("missing 'query'"); } #endregion @@ -659,7 +699,7 @@ private object PauseQueue() ShokoService.CmdProcessorHasher.Paused = true; ShokoService.CmdProcessorGeneral.Paused = true; ShokoService.CmdProcessorImages.Paused = true; - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -671,7 +711,7 @@ private object StartQueue() ShokoService.CmdProcessorHasher.Paused = false; ShokoService.CmdProcessorGeneral.Paused = false; ShokoService.CmdProcessorImages.Paused = false; - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -729,7 +769,7 @@ private object GetImagesQueue() private object PauseHasherQueue() { ShokoService.CmdProcessorHasher.Paused = true; - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -739,7 +779,7 @@ private object PauseHasherQueue() private object PauseGeneralQueue() { ShokoService.CmdProcessorGeneral.Paused = true; - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -749,7 +789,7 @@ private object PauseGeneralQueue() private object PauseImagesQueue() { ShokoService.CmdProcessorImages.Paused = true; - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -759,7 +799,7 @@ private object PauseImagesQueue() private object StartHasherQueue() { ShokoService.CmdProcessorHasher.Paused = false; - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -769,7 +809,7 @@ private object StartHasherQueue() private object StartGeneralQueue() { ShokoService.CmdProcessorGeneral.Paused = false; - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -779,7 +819,7 @@ private object StartGeneralQueue() private object StartImagesQueue() { ShokoService.CmdProcessorImages.Paused = false; - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -802,11 +842,11 @@ private object ClearHasherQueue() ShokoService.CmdProcessorHasher.Init(); - return APIStatus.statusOK(); + return APIStatus.OK(); } catch { - return APIStatus.internalError(); + return APIStatus.InternalError(); } } @@ -830,11 +870,11 @@ private object ClearGeneralQueue() ShokoService.CmdProcessorGeneral.Init(); - return APIStatus.statusOK(); + return APIStatus.OK(); } catch { - return APIStatus.internalError(); + return APIStatus.InternalError(); } } @@ -858,11 +898,11 @@ private object ClearImagesQueue() ShokoService.CmdProcessorImages.Init(); - return APIStatus.statusOK(); + return APIStatus.OK(); } catch { - return APIStatus.internalError(); + return APIStatus.InternalError(); } } @@ -876,8 +916,8 @@ private object ClearImagesQueue() /// List or RawFile or APIStatus private object GetFile() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); return para.id == 0 @@ -891,7 +931,7 @@ private object GetFile() /// private object GetMultipleFiles() { - JMMUser user = (JMMUser) this.Context.CurrentUser; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); int userID = user.JMMUserID; @@ -959,8 +999,8 @@ private object CountFiles() /// List private object GetRecentFiles(int limit = 0, int level = 0) { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (limit == 0) @@ -994,8 +1034,8 @@ private object GetRecentFiles(int limit = 0, int level = 0) /// List private List GetUnsort() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); List lst = new List(); @@ -1031,22 +1071,22 @@ private List GetUnsort() /// APIStatus private object SetFileOffset() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); // allow to offset be 0 to reset position if (para.id == 0 || para.offset < 0) { - return APIStatus.badRequest("Invalid arguments"); + return APIStatus.BadRequest("Invalid arguments"); } SVR_VideoLocal vlu = RepoFactory.VideoLocal.GetByID(para.id); if (vlu != null) { vlu.SetResumePosition(para.offset, user.JMMUserID); - return APIStatus.statusOK(); + return APIStatus.OK(); } - return APIStatus.notFound404(); + return APIStatus.NotFound(); } #region internal function @@ -1066,10 +1106,7 @@ internal object GetFileById(int file_id, int level, int uid) RawFile rawfile = new RawFile(Context, vl, level, uid); return rawfile; } - else - { - return APIStatus.notFound404(); - } + return APIStatus.NotFound(); } /// @@ -1113,18 +1150,15 @@ internal object GetAllFiles(int limit, int level, int uid) /// List or Episode private object GetEpisode() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.id == 0) { return GetAllEpisodes(user.JMMUserID, para.limit, (int) para.offset, para.level, para.all != 0); } - else - { - return GetEpisodeById(para.id, user.JMMUserID, para.level); - } + return GetEpisodeById(para.id, user.JMMUserID, para.level); } /// @@ -1133,20 +1167,17 @@ private object GetEpisode() /// Episode or APIStatis private object GetEpisodeFromName() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); - if (String.IsNullOrEmpty(para.filename)) return APIStatus.badRequest("missing 'filename'"); + if (String.IsNullOrEmpty(para.filename)) return APIStatus.BadRequest("missing 'filename'"); SVR_AnimeEpisode aep = RepoFactory.AnimeEpisode.GetByFilename(para.filename); if (aep != null) { return Episode.GenerateFromAnimeEpisode(Context, aep, user.JMMUserID, 0); } - else - { - return APIStatus.notFound404(); - } + return APIStatus.NotFound(); } /// @@ -1155,8 +1186,8 @@ private object GetEpisodeFromName() /// List private object GetRecentEpisodes() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.limit == 0) @@ -1189,17 +1220,14 @@ private object GetRecentEpisodes() /// APIStatus private object MarkEpisodeAsWatched() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.id != 0) { return MarkEpisode(true, para.id, user.JMMUserID); } - else - { - return APIStatus.badRequest("missing 'id'"); - } + return APIStatus.BadRequest("missing 'id'"); } /// @@ -1208,17 +1236,14 @@ private object MarkEpisodeAsWatched() /// APIStatus private object MarkEpisodeAsUnwatched() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.id != 0) { return MarkEpisode(false, para.id, user.JMMUserID); } - else - { - return APIStatus.badRequest("missing 'id'"); - } + return APIStatus.BadRequest("missing 'id'"); } /// @@ -1227,8 +1252,8 @@ private object MarkEpisodeAsUnwatched() /// APIStatus private object VoteOnEpisode() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.id != 0) @@ -1237,15 +1262,9 @@ private object VoteOnEpisode() { return EpisodeVote(para.id, para.score, user.JMMUserID); } - else - { - return APIStatus.badRequest("missing 'score'"); - } - } - else - { - return APIStatus.badRequest("missing 'id'"); + return APIStatus.BadRequest("missing 'score'"); } + return APIStatus.BadRequest("missing 'id'"); } /// @@ -1256,7 +1275,7 @@ private object EpisodeScrobble() { try { - Request request = this.Request; + Request request = Request; API_Call_Parameters para = this.Bind(); // statys 1-start, 2-pause, 3-stop @@ -1277,21 +1296,18 @@ private object EpisodeScrobble() switch (impl.TraktScrobble(para.id, type, para.progress, para.status)) { case 200: - return APIStatus.statusOK(); + return APIStatus.OK(); case 404: - return APIStatus.notFound404(); + return APIStatus.NotFound(); default: - return APIStatus.internalError(); + return APIStatus.InternalError(); } } - else - { - return APIStatus.badRequest(); - } + return APIStatus.BadRequest(); } catch { - return APIStatus.internalError(); + return APIStatus.InternalError(); } } @@ -1311,15 +1327,15 @@ internal object MarkEpisode(bool status, int id, int uid) SVR_AnimeEpisode ep = RepoFactory.AnimeEpisode.GetByID(id); if (ep == null) { - return APIStatus.notFound404(); + return APIStatus.NotFound(); } ep.ToggleWatchedStatus(status, true, DateTime.Now, false, false, uid, true); ep.GetAnimeSeries()?.UpdateStats(true, false, true); - return APIStatus.statusOK(); + return APIStatus.OK(); } catch (Exception ex) { - return APIStatus.internalError(ex.Message); + return APIStatus.InternalError(ex.Message); } } @@ -1377,20 +1393,11 @@ internal object GetEpisodeById(int id, int uid, int level) { return ep; } - else - { - return APIStatus.notFound404("episode not found"); - } + return APIStatus.NotFound("episode not found"); } - else - { - return APIStatus.notFound404(); - } - } - else - { - return APIStatus.badRequest("missing 'id'"); + return APIStatus.NotFound(); } + return APIStatus.BadRequest("missing 'id'"); } /// @@ -1428,17 +1435,11 @@ internal object EpisodeVote(int id, int score, int uid) //CommandRequest_VoteAnime cmdVote = new CommandRequest_VoteAnime(animeID, voteType, voteValue); //cmdVote.Save(); - return APIStatus.statusOK(); + return APIStatus.OK(); } - else - { - return APIStatus.badRequest("'score' value is wrong"); - } - } - else - { - return APIStatus.badRequest("'id' value is wrong"); + return APIStatus.BadRequest("'score' value is wrong"); } + return APIStatus.BadRequest("'id' value is wrong"); } #endregion @@ -1453,8 +1454,8 @@ internal object EpisodeVote(int id, int score, int uid) /// List or Serie private object GetSerie() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.id == 0) @@ -1462,10 +1463,7 @@ private object GetSerie() return GetAllSeries(para.nocast != 0, para.limit, (int) para.offset, para.notag != 0, para.level, para.all != 0, para.allpics != 0, para.pic, para.tagfilter); } - else - { - return GetSerieById(para.id, para.nocast != 0, para.notag != 0, para.level, para.all != 0, para.allpics != 0, para.pic, para.tagfilter); - } + return GetSerieById(para.id, para.nocast != 0, para.notag != 0, para.level, para.all != 0, para.allpics != 0, para.pic, para.tagfilter); } /// @@ -1474,8 +1472,8 @@ private object GetSerie() /// Counter private object CountSerie() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; Counter count = new Counter { count = RepoFactory.AnimeSeries.GetAll().Count @@ -1489,7 +1487,7 @@ private object CountSerie() /// List or Serie private object SeriesToday() { - JMMUser user = (JMMUser) this.Context.CurrentUser; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); // 1. get series airing @@ -1512,14 +1510,14 @@ private object SeriesToday() return DateTime.Now.DayOfWeek == ser.AirsOn.Value; }).Select(ser => Serie.GenerateFromAnimeSeries(Context, ser, user.JMMUserID, para.nocast == 1, para.notag == 1, para.level, para.all == 1, para.allpics == 1, para.pic, para.tagfilter)).OrderBy(a => a.name).ToList(); - Group group = new Group() + Group group = new Group { id = 0, name = "Airing Today", series = result, size = result.Count, summary = "Based on AniDB Episode Air Dates. Incorrect info falls on AniDB to be corrected.", - url = Request.Url, + url = Request.Url }; return group; } @@ -1530,8 +1528,8 @@ private object SeriesToday() /// List or APIStatus private object GetSeriesByFolderId() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.id != 0) @@ -1539,10 +1537,7 @@ private object GetSeriesByFolderId() return GetSeriesByFolder(para.id, user.JMMUserID, para.nocast != 0, para.notag != 0, para.level, para.all != 0, para.limit, para.allpics != 0, para.pic, para.tagfilter); } - else - { - return APIStatus.internalError("missing 'id'"); - } + return APIStatus.InternalError("missing 'id'"); } /// @@ -1551,18 +1546,15 @@ private object GetSeriesByFolderId() /// List or APIStatus private object GetSeriesInfoByFolderId() { - Request request = this.Request; - JMMUser user = (JMMUser)this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser)Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.id != 0) { return GetSeriesInfoByFolder(para.id, user.JMMUserID, para.limit, para.tagfilter); } - else - { - return APIStatus.internalError("missing 'id'"); - } + return APIStatus.InternalError("missing 'id'"); } /// @@ -1571,8 +1563,8 @@ private object GetSeriesInfoByFolderId() /// List private object GetSeriesRecent() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); List allseries = new List(); @@ -1598,17 +1590,14 @@ private object GetSeriesRecent() /// APIStatus private object MarkSerieAsWatched() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.id != 0) { return MarkSerieWatchStatus(para.id, true, user.JMMUserID); } - else - { - return APIStatus.badRequest("missing 'id'"); - } + return APIStatus.BadRequest("missing 'id'"); } /// @@ -1617,17 +1606,14 @@ private object MarkSerieAsWatched() /// APIStatus private object MarkSerieAsUnwatched() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.id != 0) { return MarkSerieWatchStatus(para.id, false, user.JMMUserID); } - else - { - return APIStatus.badRequest("missing 'id'"); - } + return APIStatus.BadRequest("missing 'id'"); } /// @@ -1636,8 +1622,8 @@ private object MarkSerieAsUnwatched() /// APIStatus private object VoteOnSerie() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.id != 0) @@ -1646,15 +1632,9 @@ private object VoteOnSerie() { return SerieVote(para.id, para.score, user.JMMUserID); } - else - { - return APIStatus.badRequest("missing 'score'"); - } - } - else - { - return APIStatus.badRequest("missing 'id'"); + return APIStatus.BadRequest("missing 'score'"); } + return APIStatus.BadRequest("missing 'id'"); } /// @@ -1663,8 +1643,8 @@ private object VoteOnSerie() /// List or APIStatus private object SearchForSerie() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.limit == 0) @@ -1677,10 +1657,7 @@ private object SearchForSerie() return Search(para.query, para.limit, para.limit_tag, (int) para.offset, para.tags, user.JMMUserID, para.nocast != 0, para.notag != 0, para.level, para.all != 0, para.fuzzy != 0, para.allpics != 0, para.pic, para.tagfilter); } - else - { - return APIStatus.badRequest("missing 'query'"); - } + return APIStatus.BadRequest("missing 'query'"); } /// @@ -1689,8 +1666,8 @@ private object SearchForSerie() /// List or APIStatus private object SearchForTag() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.limit == 0) @@ -1704,10 +1681,7 @@ private object SearchForTag() para.nocast != 0, para.notag != 0, para.level, para.all != 0, para.fuzzy != 0, para.allpics != 0, para.pic, para.tagfilter); } - else - { - return APIStatus.badRequest("missing 'query'"); - } + return APIStatus.BadRequest("missing 'query'"); } /// @@ -1717,18 +1691,15 @@ private object SearchForTag() /// Serie or APIStatus private object GetSeriesFromEpisode() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.id != 0) { return GetSerieFromEpisode(para.id, user.JMMUserID, para.nocast != 0, para.notag != 0, para.level, para.all != 0, para.allpics != 0, para.pic, para.tagfilter); } - else - { - return APIStatus.badRequest("missing 'id'"); - } + return APIStatus.BadRequest("missing 'id'"); } #region internal function @@ -1842,10 +1813,7 @@ internal object GetSerieFromEpisode(int id, int uid, bool nocast, bool notag, in { return Serie.GenerateFromAnimeSeries(Context, aep.GetAnimeSeries(), uid, nocast, notag, level, all, allpic, pic, tagfilter); } - else - { - return APIStatus.notFound404("serie not found"); - } + return APIStatus.NotFound("serie not found"); } /// @@ -1857,8 +1825,8 @@ internal object GetSerieFromEpisode(int id, int uid, bool nocast, bool notag, in /// List internal object GetAllSeries(bool nocast, int limit, int offset, bool notag, int level, bool all, bool allpic, int pic, byte tagfilter) { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; List allseries = new List(); @@ -1892,8 +1860,8 @@ internal object GetAllSeries(bool nocast, int limit, int offset, bool notag, int /// internal object GetSerieById(int series_id, bool nocast, bool notag, int level, bool all, bool allpic, int pic, byte tagfilter) { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; Serie ser = Serie.GenerateFromAnimeSeries(Context, RepoFactory.AnimeSeries.GetByID(series_id), user.JMMUserID, nocast, notag, level, all, allpic, pic, tagfilter); return ser; @@ -1911,7 +1879,7 @@ internal object MarkSerieWatchStatus(int id, bool watched, int uid) try { SVR_AnimeSeries ser = RepoFactory.AnimeSeries.GetByID(id); - if (ser == null) return APIStatus.badRequest("Series not Found"); + if (ser == null) return APIStatus.BadRequest("Series not Found"); foreach (SVR_AnimeEpisode ep in ser.GetAnimeEpisodes()) { @@ -1934,11 +1902,11 @@ internal object MarkSerieWatchStatus(int id, bool watched, int uid) ser.UpdateStats(true, true, true); - return APIStatus.statusOK(); + return APIStatus.OK(); } catch (Exception ex) { - return APIStatus.internalError(ex.Message); + return APIStatus.InternalError(ex.Message); } } @@ -1992,7 +1960,7 @@ private static void CheckTitlesFuzzy(SVR_AnimeSeries a, string query, if (string.IsNullOrEmpty(title)) continue; int k = Math.Max(Math.Min((int)(title.Length / 6D), (int)(query.Length / 6D)), 1); if (query.Length <= 4 || title.Length <= 4) k = 0; - if (Shoko.Commons.Utils.Misc.BitapFuzzySearch(title, query, k, out int newDist) == -1) continue; + if (Misc.BitapFuzzySearch(title, query, k, out int newDist) == -1) continue; if (newDist < dist) { match = title; @@ -2027,7 +1995,7 @@ private static void CheckTagsFuzzy(SVR_AnimeSeries a, string query, { if (string.IsNullOrEmpty(tag)) continue; int k = Math.Min((int)(tag.Length / 6D), (int)(query.Length / 6D)); - if (Shoko.Commons.Utils.Misc.BitapFuzzySearch(tag, query, k, out int newDist) == -1) continue; + if (Misc.BitapFuzzySearch(tag, query, k, out int newDist) == -1) continue; if (newDist < dist) { match = tag; @@ -2050,7 +2018,7 @@ private static void CheckTagsFuzzy(SVR_AnimeSeries a, string query, { if (string.IsNullOrEmpty(customTag)) continue; int k = Math.Min((int)(customTag.Length / 6D), (int)(query.Length / 6D)); - if (Shoko.Commons.Utils.Misc.BitapFuzzySearch(customTag, query, k, out int newDist) == -1) continue; + if (Misc.BitapFuzzySearch(customTag, query, k, out int newDist) == -1) continue; if (newDist < dist) { match = customTag; @@ -2085,7 +2053,7 @@ internal object Search(string query, int limit, int limit_tag, int offset, int t query = query.ToLowerInvariant(); SVR_JMMUser user = RepoFactory.JMMUser.GetByID(uid); - if (user == null) return APIStatus.unauthorized(); + if (user == null) return APIStatus.Unauthorized(); List series_list = new List(); Dictionary series = new Dictionary(); @@ -2325,7 +2293,7 @@ internal object StartsWith(string query, int limit, int uid, bool nocast, query = query.ToLowerInvariant(); SVR_JMMUser user = RepoFactory.JMMUser.GetByID(uid); - if (user == null) return APIStatus.unauthorized(); + if (user == null) return APIStatus.Unauthorized(); List series_list = new List(); Dictionary series = new Dictionary(); @@ -2367,16 +2335,16 @@ internal object SerieVote(int id, int score, int uid) { if (id <= 0) { - return APIStatus.badRequest("'id' value is wrong"); + return APIStatus.BadRequest("'id' value is wrong"); } if (score <= 0 || score > 1000) { - return APIStatus.badRequest("'score' value is wrong"); + return APIStatus.BadRequest("'score' value is wrong"); } SVR_AnimeSeries ser = RepoFactory.AnimeSeries.GetByID(id); - if (ser == null) return APIStatus.badRequest($"Series with id {id} was not found"); + if (ser == null) return APIStatus.BadRequest($"Series with id {id} was not found"); int voteType = ser.Contract.AniDBAnime.AniDBAnime.GetFinishedAiring() ? (int) AniDBVoteType.Anime : (int) AniDBVoteType.AnimeTemp; @@ -2403,10 +2371,10 @@ internal object SerieVote(int id, int score, int uid) RepoFactory.AniDB_Vote.Save(thisVote); - Commands.CommandRequest_VoteAnime cmdVote = - new Commands.CommandRequest_VoteAnime(ser.AniDB_ID, voteType, Convert.ToDecimal(score / 100)); + CommandRequest_VoteAnime cmdVote = + new CommandRequest_VoteAnime(ser.AniDB_ID, voteType, Convert.ToDecimal(score / 100)); cmdVote.Save(); - return APIStatus.statusOK(); + return APIStatus.OK(); } #endregion @@ -2418,31 +2386,31 @@ internal object SerieVote(int id, int score, int uid) private object GetCloudAccounts() { // TODO APIv2: Cloud - return APIStatus.notImplemented(); + return APIStatus.NotImplemented(); } private object GetCloudAccountsCount() { // TODO APIv2: Cloud - return APIStatus.notImplemented(); + return APIStatus.NotImplemented(); } private object AddCloudAccount() { // TODO APIv2: Cloud - return APIStatus.notImplemented(); + return APIStatus.NotImplemented(); } private object DeleteCloudAccount() { // TODO APIv2: Cloud - return APIStatus.notImplemented(); + return APIStatus.NotImplemented(); } private object RunCloudImport() { ShokoServer.RunImport(); - return APIStatus.statusOK(); + return APIStatus.OK(); } #endregion @@ -2456,19 +2424,16 @@ private object RunCloudImport() /// Filter or List private object GetFilters() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.id == 0) { return GetAllFilters(user.JMMUserID, para.nocast != 0, para.notag != 0, para.level, para.all != 0, para.allpics != 0, para.pic, para.tagfilter); } - else - { - return GetFilter(para.id, user.JMMUserID, para.nocast != 0, para.notag != 0, para.level, para.all != 0, para.allpics != 0, para.pic, para.tagfilter); - ; - } + return GetFilter(para.id, user.JMMUserID, para.nocast != 0, para.notag != 0, para.level, para.all != 0, para.allpics != 0, para.pic, para.tagfilter); + ; } #region internal function @@ -2512,13 +2477,13 @@ internal object GetAllFilters(int uid, bool nocast, bool notag, int level, bool url = APIHelper.ConstructUnsortUrl(Context), name = "Unsort" }; - filter.art.fanart.Add(new Art() + filter.art.fanart.Add(new Art { url = APIHelper.ConstructSupportImageLink(Context, "plex_unsort.png"), index = 0 }); filter.art.thumb.Add( - new Art() {url = APIHelper.ConstructSupportImageLink(Context, "plex_unsort.png"), index = 0}); + new Art {url = APIHelper.ConstructSupportImageLink(Context, "plex_unsort.png"), index = 0}); filter.size = vids.Count; filter.viewed = 0; @@ -2568,19 +2533,16 @@ internal object GetFilter(int id, int uid, bool nocast, bool notag, int level, b /// Group or List or APIStatus public object GetGroups() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.id == 0) { return GetAllGroups(user.JMMUserID, para.nocast != 0, para.notag != 0, para.level, para.all != 0, para.allpics != 0, para.pic, para.tagfilter); } - else - { - return GetGroup(para.id, user.JMMUserID, para.nocast != 0, para.notag != 0, para.level, para.all != 0, - para.filter, para.allpics != 0, para.pic, para.tagfilter); - } + return GetGroup(para.id, user.JMMUserID, para.nocast != 0, para.notag != 0, para.level, para.all != 0, + para.filter, para.allpics != 0, para.pic, para.tagfilter); } /// @@ -2589,17 +2551,14 @@ public object GetGroups() /// APIStatus private object MarkGroupAsWatched() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.id != 0) { return MarkWatchedStatusOnGroup(para.id, user.JMMUserID, true); } - else - { - return APIStatus.badRequest("missing 'id'"); - } + return APIStatus.BadRequest("missing 'id'"); } /// @@ -2608,17 +2567,14 @@ private object MarkGroupAsWatched() /// APIStatus private object MarkGroupAsUnwatched() { - Request request = this.Request; - JMMUser user = (JMMUser) this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser) Context.CurrentUser; API_Call_Parameters para = this.Bind(); if (para.id != 0) { return MarkWatchedStatusOnGroup(para.id, user.JMMUserID, false); } - else - { - return APIStatus.badRequest("missing 'id'"); - } + return APIStatus.BadRequest("missing 'id'"); } #region internal function @@ -2638,7 +2594,7 @@ internal object GetAllGroups(int uid, bool nocast, bool notag, int level, bool a List allGrps = RepoFactory.AnimeGroup_User.GetByUserID(uid); foreach (SVR_AnimeGroup_User gr in allGrps) { - SVR_AnimeGroup ag = Repositories.RepoFactory.AnimeGroup.GetByID(gr.AnimeGroupID); + SVR_AnimeGroup ag = RepoFactory.AnimeGroup.GetByID(gr.AnimeGroupID); Group grp = Group.GenerateFromAnimeGroup(Context, ag, uid, nocast, notag, level, all, 0, allpics, pic, tagfilter); grps.Add(grp); } @@ -2658,16 +2614,13 @@ internal object GetAllGroups(int uid, bool nocast, bool notag, int level, bool a /// Group or APIStatus internal object GetGroup(int id, int uid, bool nocast, bool notag, int level, bool all, int filterid, bool allpics, int pic, byte tagfilter) { - SVR_AnimeGroup ag = Repositories.RepoFactory.AnimeGroup.GetByID(id); + SVR_AnimeGroup ag = RepoFactory.AnimeGroup.GetByID(id); if (ag != null) { Group gr = Group.GenerateFromAnimeGroup(Context, ag, uid, nocast, notag, level, all, filterid, allpics, pic, tagfilter); return gr; } - else - { - return APIStatus.notFound404("group not found"); - } + return APIStatus.NotFound("group not found"); } /// @@ -2684,7 +2637,7 @@ internal object MarkWatchedStatusOnGroup(int groupid, int userid, bool watchedst SVR_AnimeGroup group = RepoFactory.AnimeGroup.GetByID(groupid); if (group == null) { - return APIStatus.notFound404("Group not Found"); + return APIStatus.NotFound("Group not Found"); } foreach (SVR_AnimeSeries series in group.GetAllSeries()) @@ -2700,14 +2653,14 @@ internal object MarkWatchedStatusOnGroup(int groupid, int userid, bool watchedst } group.TopLevelAnimeGroup.UpdateStatsFromTopLevel(true, true, false); - return APIStatus.statusOK(); + return APIStatus.OK(); } catch (Exception ex) { - APIStatus.internalError("Internal Error : " + ex); + APIStatus.InternalError("Internal Error : " + ex); LogManager.GetCurrentClassLogger().Error(ex, ex.ToString()); } - return APIStatus.badRequest(); + return APIStatus.BadRequest(); } #endregion @@ -2716,8 +2669,8 @@ internal object MarkWatchedStatusOnGroup(int groupid, int userid, bool watchedst public object GetLinks() { - Request request = this.Request; - JMMUser user = (JMMUser)this.Context.CurrentUser; + Request request = Request; + JMMUser user = (JMMUser)Context.CurrentUser; API_Call_Parameters para = this.Bind(); Dictionary links = new Dictionary(); diff --git a/Shoko.Server/API/v2/Modules/Core.cs b/Shoko.Server/API/v2/Modules/Core.cs index f36b5dd00..e923a1bfe 100644 --- a/Shoko.Server/API/v2/Modules/Core.cs +++ b/Shoko.Server/API/v2/Modules/Core.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Dynamic; using System.Globalization; using System.IO; +using System.Text; using System.Threading; using System.Threading.Tasks; using Nancy; @@ -14,14 +17,14 @@ using Shoko.Server.Commands.MAL; using Shoko.Server.Models; using Shoko.Server.PlexAndKodi; +using Shoko.Server.Providers.MyAnimeList; +using Shoko.Server.Providers.TraktTV; using Shoko.Server.Utilities; namespace Shoko.Server.API.v2.Modules { - public class Core : Nancy.NancyModule + public class Core : NancyModule { - public static int version = 1; - public Core() : base("/api") { // As this module requireAuthentication all request need to have apikey in header. @@ -137,16 +140,13 @@ public Core() : base("/api") /// private object SetPort() { - Creditentials cred = this.Bind(); + Credentials cred = this.Bind(); if (cred.port != 0) { ServerSettings.JMMServerPort = cred.port.ToString(); - return APIStatus.statusOK(); - } - else - { - return new APIMessage(400, "Port Missing"); + return APIStatus.OK(); } + return new APIMessage(400, "Port Missing"); } /// @@ -155,7 +155,7 @@ private object SetPort() /// private object GetPort() { - dynamic x = new System.Dynamic.ExpandoObject(); + dynamic x = new ExpandoObject(); x.port = int.Parse(ServerSettings.JMMServerPort); return x; } @@ -170,27 +170,18 @@ private object SetImagepath() if (imagepath.isdefault) { ServerSettings.ImagesPath = ServerSettings.DefaultImagePath; - return APIStatus.statusOK(); + return APIStatus.OK(); } - else + if (!String.IsNullOrEmpty(imagepath.path) && imagepath.path != string.Empty) { - if (!String.IsNullOrEmpty(imagepath.path) && imagepath.path != string.Empty) - { - if (Directory.Exists(imagepath.path)) - { - ServerSettings.ImagesPath = imagepath.path; - return APIStatus.statusOK(); - } - else - { - return new APIMessage(404, "Directory Not Found on Host"); - } - } - else + if (Directory.Exists(imagepath.path)) { - return new APIMessage(400, "Path Missing"); + ServerSettings.ImagesPath = imagepath.path; + return APIStatus.OK(); } + return new APIMessage(404, "Directory Not Found on Host"); } + return new APIMessage(400, "Path Missing"); } /// @@ -219,7 +210,7 @@ private object ExportConfig() } catch { - return APIStatus.internalError("Error while reading settings."); + return APIStatus.InternalError("Error while reading settings."); } } @@ -235,83 +226,86 @@ private object ImportConfig() if (raw_settings.Length != new CL_ServerSettings().ToJSON().Length) { string path = Path.Combine(ServerSettings.ApplicationPath, "temp.json"); - File.WriteAllText(path, raw_settings, System.Text.Encoding.UTF8); + File.WriteAllText(path, raw_settings, Encoding.UTF8); try { ServerSettings.LoadSettingsFromFile(path, true); - return APIStatus.statusOK(); + return APIStatus.OK(); } catch { - return APIStatus.internalError("Error while importing settings"); + return APIStatus.InternalError("Error while importing settings"); } } - else - { - return APIStatus.badRequest("Empty settings are not allowed"); - } + return APIStatus.BadRequest("Empty settings are not allowed"); } /// /// Return given setting /// - /// parameter you want to read /// private object GetSetting() { try { + // TODO Refactor Settings to a POCO that is serialized, and at runtime, build a dictionary of types to validate against Settings setting = this.Bind(); - if (setting != null) - { - var config = ServerSettings.Get(setting.setting); - if (config != null) - { - Settings return_setting = new Settings(); - return_setting.setting = setting.setting; - return_setting.value = config; - return return_setting; - } - else - { - return APIStatus.notFound404("Parameter not found."); - } - } - else + if (string.IsNullOrEmpty(setting?.setting)) return APIStatus.BadRequest("An invalid setting was passed"); + var value = typeof(ServerSettings).GetProperty(setting.setting)?.GetValue(null, null); + if (value == null) return APIStatus.BadRequest("An invalid setting was passed"); + + Settings return_setting = new Settings { - return APIStatus.badRequest("Setting was null."); - } + setting = setting.setting, + value = value.ToString() + }; + return return_setting; } catch { - return APIStatus.internalError(); + return APIStatus.InternalError(); } } /// /// Set given setting /// - /// parameter you want to write - /// value of the parameter /// private object SetSetting() { + // TODO Refactor Settings to a POCO that is serialized, and at runtime, build a dictionary of types to validate against try { Settings setting = this.Bind(); - if (setting.setting != null & setting.value != null) + if (string.IsNullOrEmpty(setting.setting)) + return APIStatus.BadRequest("An invalid setting was passed"); + + if (setting.value == null) return APIStatus.BadRequest("An invalid value was passed"); + + var property = typeof(ServerSettings).GetProperty(setting.setting); + if (property == null) return APIStatus.BadRequest("An invalid setting was passed"); + if (!property.CanWrite) return APIStatus.BadRequest("An invalid setting was passed"); + var settingType = property.PropertyType; + try { - return ServerSettings.Set(setting.setting, setting.value) - ? APIStatus.statusOK() - : APIStatus.badRequest("Setting not saved."); + var converter = TypeDescriptor.GetConverter(settingType); + if (!converter.CanConvertFrom(typeof(string))) + return APIStatus.BadRequest("An invalid value was passed"); + var value = converter.ConvertFromInvariantString(setting.value); + if (value == null) return APIStatus.BadRequest("An invalid value was passed"); + property.SetValue(null, value); } - return APIStatus.badRequest("Setting/Value was null."); + catch + { + } + + return APIStatus.BadRequest("An invalid value was passed"); } catch { - return APIStatus.internalError(); + return APIStatus.InternalError(); } -} + } #endregion @@ -323,7 +317,7 @@ private object SetSetting() /// private object SetAniDB() { - Creditentials cred = this.Bind(); + Credentials cred = this.Bind(); if (!String.IsNullOrEmpty(cred.login) && cred.login != string.Empty && !String.IsNullOrEmpty(cred.password) && cred.password != string.Empty) { @@ -333,9 +327,9 @@ private object SetAniDB() { ServerSettings.AniDB_ClientPort = cred.port.ToString(); } - return APIStatus.statusOK(); + return APIStatus.OK(); } - + return new APIMessage(400, "Login and Password missing"); } @@ -359,10 +353,10 @@ private object TestAniDB() if (ShokoService.AnidbProcessor.Login()) { ShokoService.AnidbProcessor.ForceLogout(); - return APIStatus.statusOK(); + return APIStatus.OK(); } - - return APIStatus.unauthorized(); + + return APIStatus.Unauthorized(); } /// @@ -371,7 +365,7 @@ private object TestAniDB() /// private object GetAniDB() { - Creditentials cred = new Creditentials + Credentials cred = new Credentials { login = ServerSettings.AniDB_Username, password = ServerSettings.AniDB_Password, @@ -389,7 +383,7 @@ private object SyncAniDBVotes() //TODO APIv2: Command should be split into AniDb/MAL sepereate CommandRequest_SyncMyVotes cmdVotes = new CommandRequest_SyncMyVotes(); cmdVotes.Save(); - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -399,7 +393,7 @@ private object SyncAniDBVotes() private object SyncAniDBList() { ShokoServer.SyncMyList(); - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -409,7 +403,7 @@ private object SyncAniDBList() private object UpdateAllAniDB() { Importer.RunImport_UpdateAllAniDB(); - return APIStatus.statusOK(); + return APIStatus.OK(); } #endregion @@ -422,15 +416,15 @@ private object UpdateAllAniDB() /// private object SetMAL() { - Creditentials cred = this.Bind(); + Credentials cred = this.Bind(); if (!String.IsNullOrEmpty(cred.login) && cred.login != string.Empty && !String.IsNullOrEmpty(cred.password) && cred.password != string.Empty) { ServerSettings.MAL_Username = cred.login; ServerSettings.MAL_Password = cred.password; - return APIStatus.statusOK(); + return APIStatus.OK(); } - + return new APIMessage(400, "Login and Password missing"); } @@ -440,7 +434,7 @@ private object SetMAL() /// private object GetMAL() { - Creditentials cred = new Creditentials + Credentials cred = new Credentials { login = ServerSettings.MAL_Username, password = ServerSettings.MAL_Password @@ -454,9 +448,9 @@ private object GetMAL() /// private object TestMAL() { - return Providers.MyAnimeList.MALHelper.VerifyCredentials() - ? APIStatus.statusOK() - : APIStatus.unauthorized(); + return MALHelper.VerifyCredentials() + ? APIStatus.OK() + : APIStatus.Unauthorized(); } /// @@ -466,7 +460,7 @@ private object TestMAL() private object ScanMAL() { Importer.RunImport_ScanMAL(); - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -477,7 +471,7 @@ private object DownloadFromMAL() { CommandRequest_MALDownloadStatusFromMAL cmd = new CommandRequest_MALDownloadStatusFromMAL(); cmd.Save(); - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -488,7 +482,7 @@ private object UploadToMAL() { CommandRequest_MALUploadStatusToMAL cmd = new CommandRequest_MALUploadStatusToMAL(); cmd.Save(); - return APIStatus.statusOK(); + return APIStatus.OK(); } #endregion @@ -501,13 +495,13 @@ private object UploadToMAL() /// private object SetTraktPIN() { - Creditentials cred = this.Bind(); + Credentials cred = this.Bind(); if (!String.IsNullOrEmpty(cred.token) && cred.token != string.Empty) { ServerSettings.Trakt_PIN = cred.token; - return APIStatus.statusOK(); + return APIStatus.OK(); } - + return new APIMessage(400, "Token missing"); } @@ -517,9 +511,9 @@ private object SetTraktPIN() /// private object CreateTrakt() { - return Providers.TraktTV.TraktTVHelper.EnterTraktPIN(ServerSettings.Trakt_PIN) == "Success" - ? APIStatus.statusOK() - : APIStatus.unauthorized(); + return TraktTVHelper.EnterTraktPIN(ServerSettings.Trakt_PIN) == "Success" + ? APIStatus.OK() + : APIStatus.Unauthorized(); } /// @@ -528,7 +522,7 @@ private object CreateTrakt() /// private object GetTrakt() { - Creditentials cred = new Creditentials + Credentials cred = new Credentials { token = ServerSettings.Trakt_AuthToken, refresh_token = ServerSettings.Trakt_RefreshToken @@ -546,9 +540,9 @@ private object SyncTrakt() { CommandRequest_TraktSyncCollection cmd = new CommandRequest_TraktSyncCollection(true); cmd.Save(); - return APIStatus.statusOK(); + return APIStatus.OK(); } - + return new APIMessage(204, "Trak is not enabled or you missing authtoken"); } @@ -559,7 +553,7 @@ private object SyncTrakt() private object ScanTrakt() { Importer.RunImport_ScanTrakt(); - return APIStatus.statusOK(); + return APIStatus.OK(); } #endregion @@ -573,7 +567,7 @@ private object ScanTrakt() private object ScanTvDB() { Importer.RunImport_ScanTvDB(); - return APIStatus.statusOK(); + return APIStatus.OK(); } #endregion @@ -587,7 +581,7 @@ private object ScanTvDB() private object ScanMovieDB() { Importer.RunImport_ScanMovieDB(); - return APIStatus.statusOK(); + return APIStatus.OK(); } #endregion @@ -609,20 +603,19 @@ private object GetUsers() /// private object CreateUser() { - Request request = this.Request; - SVR_JMMUser _user = (SVR_JMMUser) this.Context.CurrentUser; + SVR_JMMUser _user = (SVR_JMMUser) Context.CurrentUser; if (_user.IsAdmin == 1) { JMMUser user = this.Bind(); user.Password = Digest.Hash(user.Password); user.HideCategories = string.Empty; user.PlexUsers = string.Empty; - return new ShokoServiceImplementation().SaveUser(user) == string.Empty - ? APIStatus.statusOK() - : APIStatus.internalError(); + return new ShokoServiceImplementation().SaveUser(user) == string.Empty + ? APIStatus.OK() + : APIStatus.InternalError(); } - - return APIStatus.adminNeeded(); + + return APIStatus.AdminNeeded(); } /// @@ -631,8 +624,7 @@ private object CreateUser() /// private object ChangePassword() { - Request request = this.Request; - SVR_JMMUser user = (SVR_JMMUser) this.Context.CurrentUser; + SVR_JMMUser user = this.Bind(); return ChangePassword(user.JMMUserID); } @@ -642,17 +634,18 @@ private object ChangePassword() /// private object ChangePassword(int uid) { - Request request = this.Request; - SVR_JMMUser _user = (SVR_JMMUser) this.Context.CurrentUser; - if (_user.IsAdmin == 1) - { - SVR_JMMUser user = this.Bind(); - return new ShokoServiceImplementation().ChangePassword(uid, user.Password) == string.Empty - ? APIStatus.statusOK() - : APIStatus.internalError(); - } - - return APIStatus.adminNeeded(); + SVR_JMMUser thisuser = (SVR_JMMUser) Context.CurrentUser; + SVR_JMMUser user = this.Bind(); + if (thisuser.IsAdmin == 1) + return new ShokoServiceImplementation().ChangePassword(uid, user.Password) == string.Empty + ? APIStatus.OK() + : APIStatus.InternalError(); + if (thisuser.JMMUserID == user.JMMUserID) + return new ShokoServiceImplementation().ChangePassword(uid, user.Password) == string.Empty + ? APIStatus.OK() + : APIStatus.InternalError(); + + return APIStatus.AdminNeeded(); } /// @@ -661,17 +654,16 @@ private object ChangePassword(int uid) /// private object DeleteUser() { - Request request = this.Request; - SVR_JMMUser _user = (SVR_JMMUser) this.Context.CurrentUser; + SVR_JMMUser _user = (SVR_JMMUser) Context.CurrentUser; if (_user.IsAdmin == 1) { SVR_JMMUser user = this.Bind(); - return new ShokoServiceImplementation().DeleteUser(user.JMMUserID) == string.Empty - ? APIStatus.statusOK() - : APIStatus.internalError(); + return new ShokoServiceImplementation().DeleteUser(user.JMMUserID) == string.Empty + ? APIStatus.OK() + : APIStatus.InternalError(); } - - return APIStatus.adminNeeded(); + + return APIStatus.AdminNeeded(); } #endregion @@ -688,7 +680,7 @@ private object GetOSBaseFolder() { full_path = Environment.CurrentDirectory }; - System.IO.DirectoryInfo dir_info = new DirectoryInfo(dir.full_path); + DirectoryInfo dir_info = new DirectoryInfo(dir.full_path); dir.dir = dir_info.Name; dir.subdir = new List(); @@ -705,7 +697,7 @@ private object GetOSBaseFolder() } /// - /// Return OSFolder object of directory that was given via + /// Return OSFolder object of directory that was given via /// /// /// @@ -714,7 +706,7 @@ private object GetOSFolder(string folder) OSFolder dir = this.Bind(); if (!String.IsNullOrEmpty(dir.full_path)) { - System.IO.DirectoryInfo dir_info = new DirectoryInfo(dir.full_path); + DirectoryInfo dir_info = new DirectoryInfo(dir.full_path); dir.dir = dir_info.Name; dir.subdir = new List(); @@ -729,7 +721,7 @@ private object GetOSFolder(string folder) } return dir; } - + return new APIMessage(400, "full_path missing"); } @@ -739,7 +731,7 @@ private object GetOSFolder(string folder) /// private object GetOSDrives() { - string[] drives = System.IO.Directory.GetLogicalDrives(); + string[] drives = Directory.GetLogicalDrives(); OSFolder dir = new OSFolder { dir = "/", @@ -766,31 +758,31 @@ private object GetOSDrives() private object GetCloudAccounts() { // TODO APIv2: Cloud - return APIStatus.notImplemented(); + return APIStatus.NotImplemented(); } private object GetCloudAccountsCount() { // TODO APIv2: Cloud - return APIStatus.notImplemented(); + return APIStatus.NotImplemented(); } private object AddCloudAccount() { // TODO APIv2: Cloud - return APIStatus.notImplemented(); + return APIStatus.NotImplemented(); } private object DeleteCloudAccount() { // TODO APIv2: Cloud - return APIStatus.notImplemented(); + return APIStatus.NotImplemented(); } private object RunCloudImport() { ShokoServer.RunImport(); - return APIStatus.statusOK(); + return APIStatus.OK(); } #endregion @@ -804,7 +796,7 @@ private object RunCloudImport() private object StartRotateLogs() { ShokoServer.logrotator.Start(); - return APIStatus.statusOK(); + return APIStatus.OK(); } /// @@ -813,8 +805,8 @@ private object StartRotateLogs() /// private object SetRotateLogs() { - Request request = this.Request; - SVR_JMMUser user = (SVR_JMMUser) this.Context.CurrentUser; + Request request = Request; + SVR_JMMUser user = (SVR_JMMUser) Context.CurrentUser; Logs rotator = this.Bind(); if (user.IsAdmin == 1) @@ -824,10 +816,10 @@ private object SetRotateLogs() ServerSettings.RotateLogs_Delete = rotator.delete; ServerSettings.RotateLogs_Delete_Days = rotator.days.ToString(); - return APIStatus.statusOK(); + return APIStatus.OK(); } - - return APIStatus.adminNeeded(); + + return APIStatus.AdminNeeded(); } /// @@ -864,16 +856,16 @@ private object GetLog(int lines, int position) string log_file = LogRotator.GetCurrentLogFile(); if (string.IsNullOrEmpty(log_file)) { - return APIStatus.notFound404("Could not find current log name. Sorry"); + return APIStatus.NotFound("Could not find current log name. Sorry"); } if (!File.Exists(log_file)) { - return APIStatus.notFound404(); + return APIStatus.NotFound(); } Dictionary result = new Dictionary(); - FileStream fs = File.OpenRead(@log_file); + FileStream fs = File.OpenRead(log_file); if (position >= fs.Length) { @@ -905,9 +897,9 @@ private object UpdateImages() Importer.RunImport_UpdateTvDB(true); ShokoServer.Instance.DownloadAllImages(); - return APIStatus.statusOK(); + return APIStatus.OK(); } - + #endregion } } \ No newline at end of file diff --git a/Shoko.Server/API/v2/Modules/Database.cs b/Shoko.Server/API/v2/Modules/Database.cs deleted file mode 100644 index 6ef1cb099..000000000 --- a/Shoko.Server/API/v2/Modules/Database.cs +++ /dev/null @@ -1,181 +0,0 @@ -using System; -using System.Threading.Tasks; -using Nancy; -using Nancy.ModelBinding; -using Nancy.Security; - -namespace Shoko.Server.API.v2.Modules -{ - public class Database : Nancy.NancyModule - { - public static int version = 1; - - public Database() : base("/api/db") - { - if (!ServerSettings.FirstRun) - { - this.RequiresAuthentication(); - } - - Post["/set", true] = async (x,ct) => await Task.Factory.StartNew(SetupDB, ct); - Get["/get", true] = async (x,ct) => await Task.Factory.StartNew(GetDB, ct); - Get["/start", true] = async (x,ct) => await Task.Factory.StartNew(RunDB, ct); - Get["/check", true] = async (x,ct) => await Task.Factory.StartNew(CheckDB, ct); - } - - #region Setup - - /// - /// Setup Database and Init it - /// - /// - private object SetupDB() - { - Models.core.Database db = this.Bind(); - if (!String.IsNullOrEmpty(db.type) && db.type != string.Empty) - { - switch (db.type.ToLower()) - { - case "sqlite": - ServerSettings.DatabaseType = "SQLite"; - ServerSettings.DatabaseFile = db.path; - break; - - case "sqlserver": - ServerSettings.DatabaseType = "SQLServer"; - ServerSettings.DatabaseUsername = db.login; - ServerSettings.DatabasePassword = db.password; - ServerSettings.DatabaseName = db.table; - ServerSettings.DatabaseServer = db.server; - break; - - case "mysql": - ServerSettings.DatabaseType = "MySQL"; - ServerSettings.MySQL_Username = db.login; - ServerSettings.MySQL_Password = db.password; - ServerSettings.MySQL_SchemaName = db.table; - ServerSettings.MySQL_Hostname = db.server; - break; - } - - //ShokoServer.workerSetupDB.RunWorkerAsync(); - return HttpStatusCode.OK; - } - else - { - return HttpStatusCode.BadRequest; - } - } - - /// - /// Return Database object - /// - /// - private object GetDB() - { - Models.core.Database db = new Models.core.Database - { - type = ServerSettings.DatabaseType - }; - if (!String.IsNullOrEmpty(db.type) && db.type != string.Empty) - { - switch (db.type.ToLower()) - { - case "sqlite": - db.path = ServerSettings.DatabaseFile; - break; - - case "sqlserver": - db.login = ServerSettings.DatabaseUsername; - db.password = ServerSettings.DatabasePassword; - db.table = ServerSettings.DatabaseName; - db.server = ServerSettings.DatabaseServer; - break; - - case "mysql": - db.login = ServerSettings.MySQL_Username; - db.password = ServerSettings.MySQL_Password; - db.table = ServerSettings.MySQL_SchemaName; - db.server = ServerSettings.MySQL_Hostname; - break; - } - - return db; - } - else - { - return HttpStatusCode.BadRequest; - } - } - - /// - /// Test and run database - /// - /// - private object RunDB() - { - try - { - if (ServerState.Instance.DatabaseIsSQLite) - { - ServerSettings.DatabaseType = "SQLite"; - } - else if (ServerState.Instance.DatabaseIsSQLServer) - { - if (string.IsNullOrEmpty(ServerSettings.DatabaseName) || - string.IsNullOrEmpty(ServerSettings.DatabasePassword) - || string.IsNullOrEmpty(ServerSettings.DatabaseServer) || - string.IsNullOrEmpty(ServerSettings.DatabaseUsername)) - { - return HttpStatusCode.BadRequest; - } - } - else if (ServerState.Instance.DatabaseIsMySQL) - { - if (string.IsNullOrEmpty(ServerSettings.MySQL_SchemaName) || - string.IsNullOrEmpty(ServerSettings.MySQL_Password) - || string.IsNullOrEmpty(ServerSettings.MySQL_Hostname) || - string.IsNullOrEmpty(ServerSettings.MySQL_Username)) - { - return HttpStatusCode.BadRequest; - } - } - - ShokoServer.workerSetupDB.RunWorkerAsync(); - return HttpStatusCode.OK; - } - catch - { - return HttpStatusCode.InternalServerError; - } - } - - /// - /// check if database is valid - /// - /// - private object CheckDB() - { - if (!ShokoServer.workerSetupDB.IsBusy) - { - if (ServerState.Instance.ServerOnline) - { - ServerSettings.FirstRun = false; - return "{\"db\": 1}"; - } - else - { - ServerSettings.FirstRun = true; - return "{\"db\": 0}"; - } - } - else - { - ServerSettings.FirstRun = true; - return "{\"db\": -1}"; - } - } - - #endregion - } -} \ No newline at end of file diff --git a/Shoko.Server/API/v2/Modules/Dev.cs b/Shoko.Server/API/v2/Modules/Dev.cs index 00c4cefb2..35f6f9322 100644 --- a/Shoko.Server/API/v2/Modules/Dev.cs +++ b/Shoko.Server/API/v2/Modules/Dev.cs @@ -6,8 +6,6 @@ namespace Shoko.Server.API.v2.Modules { public class Dev : Nancy.NancyModule { - public static int version = 1; - public Dev() : base("/api/dev") { #if DEBUG diff --git a/Shoko.Server/API/v2/Modules/Image.cs b/Shoko.Server/API/v2/Modules/Image.cs index edb587b06..dba1d3284 100644 --- a/Shoko.Server/API/v2/Modules/Image.cs +++ b/Shoko.Server/API/v2/Modules/Image.cs @@ -18,8 +18,6 @@ namespace Shoko.Server.API.v2.Modules { public class Image : Nancy.NancyModule { - public static int version = 1; - private static Logger logger = LogManager.GetCurrentClassLogger(); public Image() : base("/api") @@ -32,7 +30,7 @@ public Image() : base("/api") Get["/image/validateall", true] = async (x,ct) => await Task.Factory.StartNew(() => { Importer.ValidateAllImages(); - return APIStatus.statusOK(); + return APIStatus.OK(); }, ct); } @@ -107,14 +105,14 @@ private object GetSupportImage(string name) { if (string.IsNullOrEmpty(name)) { - return APIStatus.notFound404(); + return APIStatus.NotFound(); } name = Path.GetFileNameWithoutExtension(name); System.Resources.ResourceManager man = Resources.ResourceManager; byte[] dta = (byte[]) man.GetObject(name); if ((dta == null) || (dta.Length == 0)) { - return APIStatus.notFound404(); + return APIStatus.NotFound(); } MemoryStream ms = new MemoryStream(dta); ms.Seek(0, SeekOrigin.Begin); @@ -126,7 +124,7 @@ private object GetSupportImage(string name, string ratio) { if (string.IsNullOrEmpty(name)) { - return APIStatus.notFound404(); + return APIStatus.NotFound(); } ratio = ratio.Replace(',', '.'); @@ -138,7 +136,7 @@ private object GetSupportImage(string name, string ratio) byte[] dta = (byte[]) man.GetObject(name); if ((dta == null) || (dta.Length == 0)) { - return APIStatus.notFound404(); + return APIStatus.NotFound(); } MemoryStream ms = new MemoryStream(dta); ms.Seek(0, SeekOrigin.Begin); diff --git a/Shoko.Server/API/v2/Modules/Init.cs b/Shoko.Server/API/v2/Modules/Init.cs new file mode 100644 index 000000000..479d374f2 --- /dev/null +++ b/Shoko.Server/API/v2/Modules/Init.cs @@ -0,0 +1,594 @@ +using System.Collections.Generic; +using System; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SqlServer.Management.Smo; +using Nancy; +using Nancy.ModelBinding; +using Pri.LongPath; +using Shoko.Commons; +using Shoko.Models.Client; +using Shoko.Models.Server; +using Shoko.Server.API.v2.Models.core; +using Shoko.Server.Databases; +using Shoko.Server.Utilities; +using ServerStatus = Shoko.Server.API.v2.Models.core.ServerStatus; +using Settings = Shoko.Server.API.v2.Models.core.Settings; + +namespace Shoko.Server.API.v2.Modules +{ + // ReSharper disable once UnusedMember.Global + public class Init : NancyModule + { + /// + /// + /// Preinit Module for connection testing and setup + /// Settings will be loaded prior to this starting + /// Unless otherwise noted, these will only work before server init + /// + public Init() : base("/api/init") + { + // Get version, regardless of server status + // This will work after init + Get["/version", true] = async (x,ct) => await Task.Factory.StartNew(GetVersion, ct); + + // Get the startup state + // This will work after init + Get["/status", true] = async (x, ct) => await Task.Factory.StartNew(GetServerStatus, ct); + + // Get the Default User Credentials + Get["/defaultuser", true] = async (x, ct) => await Task.Factory.StartNew(GetDefaultUserCredentials, ct); + + // Set the Default User Credentials + // Pass this a Credentials object + Post["/defaultuser", true] = async (x, ct) => await Task.Factory.StartNew(SetDefaultUserCredentials, ct); + + // Set AniDB user/pass + // Pass this a Credentials object + Post["/anidb", true] = async (x,ct) => await Task.Factory.StartNew(SetAniDB, ct); + + // Get existing AniDB user, don't provide pass + Get["/anidb", true] = async (x,ct) => await Task.Factory.StartNew(GetAniDB, ct); + + // Test AniDB login + Get["/anidb/test", true] = async (x,ct) => await Task.Factory.StartNew(TestAniDB, ct); + + // Get Database Settings + Get["/database", true] = async (x,ct) => await Task.Factory.StartNew(GetDatabaseSettings, ct); + + // Set Database Settings + Post["/database", true] = async (x,ct) => await Task.Factory.StartNew(SetDatabaseSettings, ct); + + // Test Database Connection + Get["/database/test", true] = async (x,ct) => await Task.Factory.StartNew(TestDatabaseConnection, ct); + + // Get SQL Server Instances on the Machine + Get["/database/sqlserverinstance", true] = async (x,ct) => await Task.Factory.StartNew(GetMSSQLInstances, ct); + + // Get the whole settings file + Get["/config", true] = async (x,ct) => await Task.Factory.StartNew(ExportConfig, ct); + + // Replace the whole settings file + Post["/config", true] = async (x,ct) => await Task.Factory.StartNew(ImportConfig, ct); + + // Get a single setting value + Get["/setting", true] = async (x, ct) => await Task.Factory.StartNew(GetSetting, ct); + + // Set a single setting value + Patch["/setting", true] = async (x, ct) => await Task.Factory.StartNew(SetSetting, ct); + + // Start the server + Get["/startserver", true] = async (x, ct) => await Task.Factory.StartNew(StartServer, ct); + } + + /// + /// Return current version of ShokoServer and several modules + /// This will work after init + /// + /// + private object GetVersion() + { + List list = new List(); + + ComponentVersion version = new ComponentVersion + { + version = Utils.GetApplicationVersion(), + name = "server" + }; + list.Add(version); + + string versionExtra = Utils.GetApplicationExtraVersion(); + + if (!string.IsNullOrEmpty(versionExtra)) + { + version = new ComponentVersion + { + version = versionExtra, + name = "servercommit" + }; + list.Add(version); + } + + version = new ComponentVersion + { + version = Assembly.GetAssembly(typeof(FolderMappings)).GetName().Version.ToString(), + name = "commons" + }; + list.Add(version); + + version = new ComponentVersion + { + version = Assembly.GetAssembly(typeof(AniDB_Anime)).GetName().Version.ToString(), + name = "models" + }; + list.Add(version); + + version = new ComponentVersion + { + version = Assembly.GetAssembly(typeof(INancyModule)).GetName().Version.ToString(), + name = "Nancy" + }; + list.Add(version); + + string dllpath = Assembly.GetEntryAssembly().Location; + dllpath = Path.GetDirectoryName(dllpath); + dllpath = Path.Combine(dllpath, "x86"); + dllpath = Path.Combine(dllpath, "MediaInfo.dll"); + + if (File.Exists(dllpath)) + { + version = new ComponentVersion + { + version = FileVersionInfo.GetVersionInfo(dllpath).FileVersion, + name = "MediaInfo" + }; + list.Add(version); + } + else + { + dllpath = Assembly.GetEntryAssembly().Location; + dllpath = Path.GetDirectoryName(dllpath); + dllpath = Path.Combine(dllpath, "x64"); + dllpath = Path.Combine(dllpath, "MediaInfo.dll"); + if (File.Exists(dllpath)) + { + version = new ComponentVersion + { + version = FileVersionInfo.GetVersionInfo(dllpath).FileVersion, + name = "MediaInfo" + }; + list.Add(version); + } + else + { + version = new ComponentVersion + { + version = @"DLL not found, using internal", + name = "MediaInfo" + }; + list.Add(version); + } + } + + if (File.Exists("webui//index.ver")) + { + string webui_version = File.ReadAllText("webui//index.ver"); + string[] versions = webui_version.Split('>'); + if (versions.Length == 2) + { + version = new ComponentVersion + { + name = "webui/" + versions[0], + version = versions[1] + }; + list.Add(version); + } + } + + return list; + } + + /// + /// Gets various information about the startup status of the server + /// This will work after init + /// + /// + private object GetServerStatus() + { + ServerStatus status = new ServerStatus + { + server_started = ServerState.Instance.ServerOnline, + startup_state = ServerState.Instance.CurrentSetupStatus, + first_run = ServerSettings.FirstRun, + startup_failed = ServerState.Instance.StartupFailed, + startup_failed_error_message = ServerState.Instance.StartupFailedMessage + }; + return status; + } + + /// + /// Gets the Default user's credentials. Will only return on first run + /// + /// + private object GetDefaultUserCredentials() + { + if (!ServerSettings.FirstRun || ServerState.Instance.ServerOnline || ServerState.Instance.ServerStarting) + return APIStatus.BadRequest("You may only request the default user's credentials on first run"); + + return new Credentials + { + login = ServerSettings.DefaultUserUsername, + password = ServerSettings.DefaultUserPassword + }; + } + + /// + /// Sets the default user's credentials + /// + /// + private object SetDefaultUserCredentials() + { + if (!ServerSettings.FirstRun || ServerState.Instance.ServerOnline || ServerState.Instance.ServerStarting) + return APIStatus.BadRequest("You may only set the default user's credentials on first run"); + + try + { + Credentials credentials = this.Bind(); + ServerSettings.DefaultUserUsername = credentials.login; + ServerSettings.DefaultUserPassword = credentials.password; + return APIStatus.OK(); + } + catch + { + return APIStatus.InternalError(); + } + } + + /// + /// Starts the server, or does nothing + /// + /// + private object StartServer() + { + if (ServerState.Instance.ServerOnline) return APIStatus.BadRequest("Already Running"); + if (ServerState.Instance.ServerStarting) return APIStatus.BadRequest("Already Starting"); + ShokoServer.RunWorkSetupDB(); + return APIStatus.OK(); + } + + #region 01. AniDB + + /// + /// Set AniDB account credentials with a Credentials object + /// + /// + private object SetAniDB() + { + if (ServerState.Instance.ServerOnline || ServerState.Instance.ServerStarting) + return APIStatus.BadRequest("You may only do this before server init"); + + Credentials cred = this.Bind(); + if (string.IsNullOrEmpty(cred.login) || string.IsNullOrEmpty(cred.password)) + return new APIMessage(400, "Login and Password missing"); + + ServerSettings.AniDB_Username = cred.login; + ServerSettings.AniDB_Password = cred.password; + if (cred.port != 0) + ServerSettings.AniDB_ClientPort = cred.port.ToString(); + if (!string.IsNullOrEmpty(cred.apikey)) + ServerSettings.AniDB_AVDumpKey = cred.apikey; + if (cred.apiport != 0) + ServerSettings.AniDB_AVDumpClientPort = cred.apiport.ToString(); + + return APIStatus.OK(); + } + + /// + /// Test AniDB Creditentials + /// + /// + private object TestAniDB() + { + if (ServerState.Instance.ServerOnline || ServerState.Instance.ServerStarting) + return APIStatus.BadRequest("You may only do this before server init"); + + ShokoService.AnidbProcessor.ForceLogout(); + ShokoService.AnidbProcessor.CloseConnections(); + + Thread.Sleep(1000); + + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); + + ShokoService.AnidbProcessor.Init(ServerSettings.AniDB_Username, ServerSettings.AniDB_Password, + ServerSettings.AniDB_ServerAddress, + ServerSettings.AniDB_ServerPort, ServerSettings.AniDB_ClientPort); + + if (!ShokoService.AnidbProcessor.Login()) return APIStatus.Unauthorized(); + ShokoService.AnidbProcessor.ForceLogout(); + + return APIStatus.OK(); + } + + /// + /// Return existing login and ports for AniDB + /// + /// + private object GetAniDB() + { + if (ServerState.Instance.ServerOnline || ServerState.Instance.ServerStarting) + return APIStatus.BadRequest("You may only do this before server init"); + + try + { + Credentials cred = new Credentials + { + login = ServerSettings.AniDB_Username, + port = int.Parse(ServerSettings.AniDB_ClientPort), + apiport = int.Parse(ServerSettings.AniDB_AVDumpClientPort) + }; + return cred; + } + catch + { + return APIStatus.InternalError( + "The ports are not set as integers. Set them and try again.\n\rThe default values are:\n\rAniDB Client Port: 4556\n\rAniDB AVDump Client Port: 4557"); + } + } + + #endregion + + #region 02. Database + + /// + /// Get Database Settings + /// + /// + private object GetDatabaseSettings() + { + if (ServerState.Instance.ServerOnline || ServerState.Instance.ServerStarting) + return APIStatus.BadRequest("You may only do this before server init"); + + var settings = new DatabaseSettings + { + db_type = ServerSettings.DatabaseType, + mysql_hostname = ServerSettings.MySQL_Hostname, + mysql_password = ServerSettings.MySQL_Password, + mysql_schemaname = ServerSettings.MySQL_SchemaName, + mysql_username = ServerSettings.MySQL_Username, + sqlite_databasefile = ServerSettings.DatabaseFile, + sqlserver_databasename = ServerSettings.DatabaseName, + sqlserver_databaseserver = ServerSettings.DatabaseServer, + sqlserver_password = ServerSettings.DatabasePassword, + sqlserver_username = ServerSettings.DatabaseUsername + }; + + return settings; + } + + /// + /// Set Database Settings + /// + /// + private object SetDatabaseSettings() + { + if (ServerState.Instance.ServerOnline || ServerState.Instance.ServerStarting) + return APIStatus.BadRequest("You may only do this before server init"); + + DatabaseSettings settings = this.Bind(); + string dbtype = settings.db_type.Trim(); + if (dbtype.Equals(Constants.DatabaseType.MySQL, StringComparison.InvariantCultureIgnoreCase)) + { + if (string.IsNullOrEmpty(settings.mysql_hostname) || string.IsNullOrEmpty(settings.mysql_password) || + string.IsNullOrEmpty(settings.mysql_schemaname) || string.IsNullOrEmpty(settings.mysql_username)) + return APIStatus.BadRequest("An invalid setting was passed"); + ServerSettings.DatabaseType = Constants.DatabaseType.MySQL; + ServerSettings.MySQL_Hostname = settings.mysql_hostname; + ServerSettings.MySQL_Password = settings.mysql_password; + ServerSettings.MySQL_SchemaName = settings.mysql_schemaname; + ServerSettings.MySQL_Username = settings.mysql_username; + return APIStatus.OK(); + } + if (dbtype.Equals(Constants.DatabaseType.SqlServer, StringComparison.InvariantCultureIgnoreCase)) + { + if (string.IsNullOrEmpty(settings.sqlserver_databasename) || string.IsNullOrEmpty(settings.sqlserver_databaseserver) || + string.IsNullOrEmpty(settings.sqlserver_password) || string.IsNullOrEmpty(settings.sqlserver_username)) + return APIStatus.BadRequest("An invalid setting was passed"); + ServerSettings.DatabaseType = Constants.DatabaseType.SqlServer; + ServerSettings.DatabaseServer = settings.sqlserver_databaseserver; + ServerSettings.DatabaseName = settings.sqlserver_databasename; + ServerSettings.DatabaseUsername = settings.sqlserver_username; + ServerSettings.DatabasePassword = settings.sqlserver_password; + return APIStatus.OK(); + } + if (dbtype.Equals(Constants.DatabaseType.Sqlite, StringComparison.InvariantCultureIgnoreCase)) + { + if (string.IsNullOrEmpty(settings.sqlite_databasefile)) + return APIStatus.BadRequest("An invalid setting was passed"); + ServerSettings.DatabaseType = Constants.DatabaseType.Sqlite; + ServerSettings.DatabaseFile = settings.sqlite_databasefile; + return APIStatus.OK(); + } + return APIStatus.BadRequest("An invalid setting was passed"); + } + + /// + /// Test Database Connection with Current Settings + /// + /// 200 if connection successful, 400 otherwise + private object TestDatabaseConnection() + { + if (ServerState.Instance.ServerOnline || ServerState.Instance.ServerStarting) + return APIStatus.BadRequest("You may only do this before server init"); + + if (ServerSettings.DatabaseType.Equals(Constants.DatabaseType.MySQL, + StringComparison.InvariantCultureIgnoreCase) && new MySQL().TestConnection()) + return APIStatus.OK(); + + if (ServerSettings.DatabaseType.Equals(Constants.DatabaseType.SqlServer, + StringComparison.InvariantCultureIgnoreCase) && new SQLServer().TestConnection()) + return APIStatus.OK(); + + if (ServerSettings.DatabaseType.Equals(Constants.DatabaseType.Sqlite, + StringComparison.InvariantCultureIgnoreCase)) + return APIStatus.OK(); + + return APIStatus.BadRequest("Failed to Connect"); + } + + /// + /// Get SQL Server Instances Running on this Machine + /// + /// List of strings that may be passed as sqlserver_databaseserver + private object GetMSSQLInstances() + { + if (ServerState.Instance.ServerOnline || ServerState.Instance.ServerStarting) + return APIStatus.BadRequest("You may only do this before server init"); + + List instances = new List(); + + DataTable dt = SmoApplication.EnumAvailableSqlServers(); + if (dt?.Rows.Count > 0) instances.AddRange(from DataRow row in dt.Rows select row[0].ToString()); + + return instances; + } + #endregion + + #region 03. Settings + + /// + /// Return body of current working settings.json - this could act as backup + /// + /// + private object ExportConfig() + { + if (ServerState.Instance.ServerOnline || ServerState.Instance.ServerStarting) + return APIStatus.BadRequest("You may only do this before server init"); + + try + { + return ServerSettings.appSettings; + } + catch + { + return APIStatus.InternalError("Error while reading settings."); + } + } + + /// + /// Import config file that was sent to in API body - this act as import from backup + /// + /// APIStatus + private object ImportConfig() + { + if (ServerState.Instance.ServerOnline || ServerState.Instance.ServerStarting) + return APIStatus.BadRequest("You may only do this before server init"); + + CL_ServerSettings settings = this.Bind(); + string raw_settings = settings.ToJSON(); + + if (raw_settings.Length == new CL_ServerSettings().ToJSON().Length) + return APIStatus.BadRequest("Empty settings are not allowed"); + + string path = Path.Combine(ServerSettings.ApplicationPath, "temp.json"); + File.WriteAllText(path, raw_settings, System.Text.Encoding.UTF8); + try + { + ServerSettings.LoadSettingsFromFile(path, true); + return APIStatus.OK(); + } + catch + { + return APIStatus.InternalError("Error while importing settings"); + } + } + + /// + /// Return given setting + /// + /// + private object GetSetting() + { + if (ServerState.Instance.ServerOnline || ServerState.Instance.ServerStarting) + return APIStatus.BadRequest("You may only do this before server init"); + + try + { + // TODO Refactor Settings to a POCO that is serialized, and at runtime, build a dictionary of types to validate against + Settings setting = this.Bind(); + if (string.IsNullOrEmpty(setting?.setting)) return APIStatus.BadRequest("An invalid setting was passed"); + try + { + var value = typeof(ServerSettings).GetProperty(setting.setting)?.GetValue(null, null); + if (value == null) return APIStatus.BadRequest("An invalid setting was passed"); + + Settings return_setting = new Settings + { + setting = setting.setting, + value = value.ToString() + }; + return return_setting; + } + catch + { + return APIStatus.BadRequest("An invalid setting was passed"); + } + } + catch + { + return APIStatus.InternalError(); + } + } + + /// + /// Set given setting + /// + /// + private object SetSetting() + { + if (ServerState.Instance.ServerOnline || ServerState.Instance.ServerStarting) + return APIStatus.BadRequest("You may only do this before server init"); + + // TODO Refactor Settings to a POCO that is serialized, and at runtime, build a dictionary of types to validate against + try + { + Settings setting = this.Bind(); + if (string.IsNullOrEmpty(setting.setting)) + return APIStatus.BadRequest("An invalid setting was passed"); + + if (setting.value == null) return APIStatus.BadRequest("An invalid value was passed"); + + var property = typeof(ServerSettings).GetProperty(setting.setting); + if (property == null) return APIStatus.BadRequest("An invalid setting was passed"); + if (!property.CanWrite) return APIStatus.BadRequest("An invalid setting was passed"); + var settingType = property.PropertyType; + try + { + var converter = TypeDescriptor.GetConverter(settingType); + if (!converter.CanConvertFrom(typeof(string))) + return APIStatus.BadRequest("An invalid value was passed"); + var value = converter.ConvertFromInvariantString(setting.value); + if (value == null) return APIStatus.BadRequest("An invalid value was passed"); + property.SetValue(null, value); + } + catch + { + // ignore, we are returning the error below + } + + return APIStatus.BadRequest("An invalid value was passed"); + } + catch + { + return APIStatus.InternalError(); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Shoko.Server/API/v2/Modules/PlexWebhook.cs b/Shoko.Server/API/v2/Modules/PlexWebhook.cs index 61c11b3e7..d97517f87 100644 --- a/Shoko.Server/API/v2/Modules/PlexWebhook.cs +++ b/Shoko.Server/API/v2/Modules/PlexWebhook.cs @@ -47,7 +47,7 @@ object WebhookPost() break; } - return APIStatus.statusOK(); + return APIStatus.OK(); } #region Plex events @@ -218,21 +218,21 @@ public PlexWebhookAuthenticated() : base("/plex") Get["/sync", true] = async (x, ct) => await Task.Factory.StartNew(() => { new CommandRequest_PlexSyncWatched((JMMUser) this.Context.CurrentUser).Save(); - return APIStatus.statusOK(); + return APIStatus.OK(); }); Get["/sync/all", true] = async (x, ct) => await Task.Factory.StartNew(() => { - if (((JMMUser) this.Context.CurrentUser).IsAdmin != 1) return APIStatus.adminNeeded(); + if (((JMMUser) this.Context.CurrentUser).IsAdmin != 1) return APIStatus.AdminNeeded(); ShokoServer.Instance.SyncPlex(); - return APIStatus.statusOK(); + return APIStatus.OK(); }); Get["/sync/{id}", true] = async (x, ct) => await Task.Factory.StartNew(() => { - if (((JMMUser)this.Context.CurrentUser).IsAdmin != 1) return APIStatus.adminNeeded(); + if (((JMMUser)this.Context.CurrentUser).IsAdmin != 1) return APIStatus.AdminNeeded(); JMMUser user = RepoFactory.JMMUser.GetByID(x.id); ShokoServer.Instance.SyncPlex(); - return APIStatus.statusOK(); + return APIStatus.OK(); }); #if DEBUG Get["/test/{id}"] = o => Response.AsJson(CallPlexHelper(h => h.GetPlexSeries((int) o.id))); diff --git a/Shoko.Server/API/v2/Modules/Unauth.cs b/Shoko.Server/API/v2/Modules/Unauth.cs deleted file mode 100644 index 293ffe30e..000000000 --- a/Shoko.Server/API/v2/Modules/Unauth.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Pri.LongPath; -using Shoko.Server.API.v2.Models.core; - -namespace Shoko.Server.API.v2.Modules -{ - public class Unauth : Nancy.NancyModule - { - public static int version = 1; - - public Unauth() - { - Get["/api/version", true] = async (x,ct) => await Task.Factory.StartNew(GetVersion, ct); - } - - /// - /// Return current version of JMMServer - /// - /// - private object GetVersion() - { - List list = new List(); - - ComponentVersion version = new ComponentVersion - { - version = System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString(), - name = "server" - }; - list.Add(version); - - version = new ComponentVersion - { - name = "auth_module", - version = Auth.version.ToString() - }; - list.Add(version); - - version = new ComponentVersion - { - name = "common_module", - version = Common.version.ToString() - }; - list.Add(version); - - version = new ComponentVersion - { - name = "core_module", - version = Core.version.ToString() - }; - list.Add(version); - - version = new ComponentVersion - { - name = "database_module", - version = Database.version.ToString() - }; - list.Add(version); - - version = new ComponentVersion - { - name = "dev_module", - version = Dev.version.ToString() - }; - list.Add(version); - - version = new ComponentVersion - { - name = "unauth_module", - version = Unauth.version.ToString() - }; - list.Add(version); - - version = new ComponentVersion - { - name = "webui_module", - version = Webui.version.ToString() - }; - list.Add(version); - - if (File.Exists("webui//index.ver")) - { - string webui_version = File.ReadAllText("webui//index.ver"); - string[] versions = webui_version.Split('>'); - if (versions.Length == 2) - { - version = new ComponentVersion - { - name = "webui/" + versions[0], - version = versions[1] - }; - list.Add(version); - } - } - - return list; - } - } -} \ No newline at end of file diff --git a/Shoko.Server/API/v2/Modules/Version.cs b/Shoko.Server/API/v2/Modules/Version.cs new file mode 100644 index 000000000..e815f4590 --- /dev/null +++ b/Shoko.Server/API/v2/Modules/Version.cs @@ -0,0 +1,126 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Threading.Tasks; +using Nancy; +using Pri.LongPath; +using Shoko.Commons; +using Shoko.Models.Server; +using Shoko.Server.API.v2.Models.core; + +namespace Shoko.Server.API.v2.Modules +{ + public class Version : NancyModule + { + public Version() + { + Get["/api/version", true] = async (x,ct) => await Task.Factory.StartNew(GetVersion, ct); + } + + /// + /// Return current version of ShokoServer and several modules + /// + /// + private object GetVersion() + { + List list = new List(); + + ComponentVersion version = new ComponentVersion + { + version = Utils.GetApplicationVersion(), + name = "server" + }; + list.Add(version); + + string versionExtra = Utils.GetApplicationExtraVersion(); + + if (!string.IsNullOrEmpty(versionExtra)) + { + version = new ComponentVersion + { + version = versionExtra, + name = "servercommit" + }; + list.Add(version); + } + + version = new ComponentVersion + { + version = Assembly.GetAssembly(typeof(FolderMappings)).GetName().Version.ToString(), + name = "commons" + }; + list.Add(version); + + version = new ComponentVersion + { + version = Assembly.GetAssembly(typeof(AniDB_Anime)).GetName().Version.ToString(), + name = "models" + }; + list.Add(version); + + version = new ComponentVersion + { + version = Assembly.GetAssembly(typeof(INancyModule)).GetName().Version.ToString(), + name = "Nancy" + }; + list.Add(version); + + string dllpath = Assembly.GetEntryAssembly().Location; + dllpath = Path.GetDirectoryName(dllpath); + dllpath = Path.Combine(dllpath, "x86"); + dllpath = Path.Combine(dllpath, "MediaInfo.dll"); + + if (File.Exists(dllpath)) + { + version = new ComponentVersion + { + version = FileVersionInfo.GetVersionInfo(dllpath).FileVersion, + name = "MediaInfo" + }; + list.Add(version); + } + else + { + dllpath = Assembly.GetEntryAssembly().Location; + dllpath = Path.GetDirectoryName(dllpath); + dllpath = Path.Combine(dllpath, "x64"); + dllpath = Path.Combine(dllpath, "MediaInfo.dll"); + if (File.Exists(dllpath)) + { + version = new ComponentVersion + { + version = FileVersionInfo.GetVersionInfo(dllpath).FileVersion, + name = "MediaInfo" + }; + list.Add(version); + } + else + { + version = new ComponentVersion + { + version = @"DLL not found, using internal", + name = "MediaInfo" + }; + list.Add(version); + } + } + + if (File.Exists("webui//index.ver")) + { + string webui_version = File.ReadAllText("webui//index.ver"); + string[] versions = webui_version.Split('>'); + if (versions.Length == 2) + { + version = new ComponentVersion + { + name = "webui/" + versions[0], + version = versions[1] + }; + list.Add(version); + } + } + + return list; + } + } +} \ No newline at end of file diff --git a/Shoko.Server/API/v2/Modules/Webui.cs b/Shoko.Server/API/v2/Modules/Webui.cs index 6194da907..08956dda0 100644 --- a/Shoko.Server/API/v2/Modules/Webui.cs +++ b/Shoko.Server/API/v2/Modules/Webui.cs @@ -11,8 +11,6 @@ namespace Shoko.Server.API.v2.Modules { public class Webui : Nancy.NancyModule { - public static int version = 1; - public Webui() : base("/api/webui") { this.RequiresAuthentication(); @@ -88,7 +86,7 @@ internal object WebUIGetUrlAndUpdate(string tag_name, string channel) } catch { - return APIStatus.internalError(); + return APIStatus.InternalError(); } } @@ -108,21 +106,21 @@ internal object WebUIUpdate(string url, string channel, string version) //download latest version var client = new System.Net.WebClient(); client.Headers.Add("User-Agent", "shokoserver"); - client.DownloadFile(url, "webui\\latest.zip"); + client.DownloadFile(url, Path.Combine("webui", "latest.zip")); //create 'old' dictionary - if (!Directory.Exists("webui\\old")) + if (!Directory.Exists(Path.Combine("webui", "old"))) { - System.IO.Directory.CreateDirectory("webui\\old"); + System.IO.Directory.CreateDirectory(Path.Combine("webui", "old")); } try { //move all directories and files to 'old' folder as fallback recovery foreach (string dir in directories) { - if (Directory.Exists(dir) && dir != "webui\\old" && dir != "webui\\tweak") + if (Directory.Exists(dir) && dir != Path.Combine("webui", "old") && dir != Path.Combine("webui", "tweak")) { - string n_dir = dir.Replace("webui", "webui\\old"); + string n_dir = dir.Replace("webui", Path.Combine("webui", "old")); Directory.Move(dir, n_dir); } } @@ -130,7 +128,7 @@ internal object WebUIUpdate(string url, string channel, string version) { if (File.Exists(file)) { - string n_file = file.Replace("webui", "webui\\old"); + string n_file = file.Replace("webui", Path.Combine("webui", "old")); File.Move(file, n_file); } } @@ -138,20 +136,20 @@ internal object WebUIUpdate(string url, string channel, string version) try { //extract latest webui - System.IO.Compression.ZipFile.ExtractToDirectory("webui\\latest.zip", "webui"); + System.IO.Compression.ZipFile.ExtractToDirectory(Path.Combine("webui", "latest.zip"), "webui"); //clean because we already have working updated webui - Directory.Delete("webui\\old", true); - File.Delete("webui\\latest.zip"); + Directory.Delete(Path.Combine("webui", "old"), true); + File.Delete(Path.Combine("webui", "latest.zip")); //save version type>version that was installed successful - if (File.Exists("webui\\index.ver")) + if (File.Exists(Path.Combine("webui", "index.ver"))) { - File.Delete("webui\\index.ver"); + File.Delete(Path.Combine("webui", "index.ver")); } - File.AppendAllText("webui\\index.ver", channel + ">" + version); + File.AppendAllText(Path.Combine("webui", "index.ver"), channel + ">" + version); - return APIStatus.statusOK(); + return APIStatus.OK(); } catch { @@ -304,12 +302,12 @@ private object GetWebUIConfig() } catch { - return APIStatus.internalError("error while reading webui settings"); + return APIStatus.InternalError("error while reading webui settings"); } } else { - return APIStatus.notFound404(); + return APIStatus.NotFound(); } } @@ -325,11 +323,11 @@ private object SetWebUIConfig() try { ServerSettings.WebUI_Settings = JsonConvert.SerializeObject(settings); - return APIStatus.statusOK(); + return APIStatus.OK(); } catch { - return APIStatus.internalError("error at saving webui settings"); + return APIStatus.InternalError("error at saving webui settings"); } } else @@ -345,9 +343,9 @@ private object SetWebUIConfig() private object GetWebUIThemes() { List files = new List(); - if (Directory.Exists("webui\\tweak")) + if (Directory.Exists(Path.Combine("webui", "tweak"))) { - DirectoryInfo dir_info = new DirectoryInfo("webui\\tweak"); + DirectoryInfo dir_info = new DirectoryInfo(Path.Combine("webui", "tweak")); foreach (FileInfo info in dir_info.GetFiles("*.css")) { v2.Models.core.OSFile file = new v2.Models.core.OSFile diff --git a/Shoko.Server/AniDBHelper.cs b/Shoko.Server/AniDBHelper.cs index 57f10aa45..3ceebbad5 100644 --- a/Shoko.Server/AniDBHelper.cs +++ b/Shoko.Server/AniDBHelper.cs @@ -7,29 +7,31 @@ using System.Net.Sockets; using System.Text; using System.Threading; +using System.Timers; using AniDBAPI; using AniDBAPI.Commands; using NHibernate; using NLog; -using System.Windows; +using Shoko.Commons.Properties; using Shoko.Models.Enums; using Shoko.Models.Interfaces; using Shoko.Models.Server; using Shoko.Server.Commands; using Shoko.Server.Databases; -using Shoko.Server.Models; using Shoko.Server.Extensions; +using Shoko.Server.Models; using Shoko.Server.Repositories; using Shoko.Server.Repositories.NHibernate; +using Timer = System.Timers.Timer; namespace Shoko.Server { public class AniDBHelper { - private static Logger logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); // we use this lock to make don't try and access AniDB too much (UDP and HTTP) - private object lockAniDBConnections = new object(); + private readonly object lockAniDBConnections = new object(); private IPEndPoint localIpEndPoint; private IPEndPoint remoteIpEndPoint; @@ -41,23 +43,16 @@ public class AniDBHelper private string serverName = string.Empty; private string serverPort = string.Empty; private string clientPort = string.Empty; - private Encoding encoding; - - System.Timers.Timer logoutTimer = null; - private DateTime? banTime = null; + private Timer logoutTimer; - public DateTime? BanTime - { - get { return banTime; } - set { banTime = value; } - } + public DateTime? BanTime { get; set; } - private bool isBanned = false; + private bool isBanned; public bool IsBanned { - get { return isBanned; } + get => isBanned; set { @@ -79,7 +74,7 @@ public bool IsBanned public string BanOrigin { - get { return banOrigin; } + get => banOrigin; set { banOrigin = value; @@ -87,11 +82,11 @@ public string BanOrigin } } - private bool isInvalidSession = false; + private bool isInvalidSession; public bool IsInvalidSession { - get { return isInvalidSession; } + get => isInvalidSession; set { @@ -100,64 +95,33 @@ public bool IsInvalidSession } } - private bool isLoggedOn = false; + private bool isLoggedOn; public bool IsLoggedOn { - get { return isLoggedOn; } - set { isLoggedOn = value; } + get => isLoggedOn; + set => isLoggedOn = value; } - private bool waitingOnResponse = false; - - public bool WaitingOnResponse - { - get { return waitingOnResponse; } - set { waitingOnResponse = value; } - } - - private DateTime? waitingOnResponseTime = null; - - public DateTime? WaitingOnResponseTime - { - get { return waitingOnResponseTime; } - set { waitingOnResponseTime = value; } - } + public bool WaitingOnResponse { get; set; } - private int? extendPauseSecs = null; + public DateTime? WaitingOnResponseTime { get; set; } - public int? ExtendPauseSecs - { - get { return extendPauseSecs; } - set { extendPauseSecs = value; } - } + public int? ExtendPauseSecs { get; set; } - private string extendPauseReason = string.Empty; - private bool networkAvailable; - public bool IsNetworkAvailable - { - get => networkAvailable; - } + public bool IsNetworkAvailable { private set; get; } - public string ExtendPauseReason - { - get { return extendPauseReason; } - set { extendPauseReason = value; } - } + public string ExtendPauseReason { get; set; } = string.Empty; public static event EventHandler LoginFailed; - public AniDBHelper() - { - } - public void ExtendPause(int secsToPause, string pauseReason) { Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); ExtendPauseSecs = secsToPause; ExtendPauseReason = pauseReason; - ServerInfo.Instance.ExtendedPauseString = string.Format(Commons.Properties.Resources.AniDB_Paused, + ServerInfo.Instance.ExtendedPauseString = string.Format(Resources.AniDB_Paused, secsToPause, pauseReason); ServerInfo.Instance.HasExtendedPause = true; @@ -181,13 +145,13 @@ public void Init(string userName, string password, string serverName, string ser this.serverPort = serverPort; this.clientPort = clientPort; - this.isLoggedOn = false; + isLoggedOn = false; - if (!BindToLocalPort()) networkAvailable = false; - if (!BindToRemotePort()) networkAvailable = false; + if (!BindToLocalPort()) IsNetworkAvailable = false; + if (!BindToRemotePort()) IsNetworkAvailable = false; - logoutTimer = new System.Timers.Timer(); - logoutTimer.Elapsed += new System.Timers.ElapsedEventHandler(LogoutTimer_Elapsed); + logoutTimer = new Timer(); + logoutTimer.Elapsed += LogoutTimer_Elapsed; logoutTimer.Interval = 5000; // Set the Interval to 5 seconds. logoutTimer.Enabled = true; logoutTimer.AutoReset = true; @@ -213,7 +177,7 @@ public void CloseConnections() soUdp = null; } - void LogoutTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + void LogoutTimer_Elapsed(object sender, ElapsedEventArgs e) { TimeSpan tsAniDBUDPTemp = DateTime.Now - ShokoService.LastAniDBUDPMessage; if (ExtendPauseSecs.HasValue && tsAniDBUDPTemp.TotalSeconds >= ExtendPauseSecs.Value) @@ -232,7 +196,7 @@ void LogoutTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) TimeSpan ts = DateTime.Now - WaitingOnResponseTime.Value; ServerInfo.Instance.WaitingOnResponseAniDBUDPString = - string.Format(Commons.Properties.Resources.AniDB_ResponseWaitSeconds, + string.Format(Resources.AniDB_ResponseWaitSeconds, ts.TotalSeconds); } } @@ -261,7 +225,7 @@ void LogoutTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); - string msg = string.Format(Commons.Properties.Resources.AniDB_LastMessage, + string msg = string.Format(Resources.AniDB_LastMessage, tsAniDBUDP.TotalSeconds); if (tsAniDBNonPing.TotalSeconds > Constants.ForceLogoutPeriod) // after 10 minutes @@ -280,9 +244,9 @@ private void SetWaitingOnResponse(bool isWaiting) if (isWaiting) ServerInfo.Instance.WaitingOnResponseAniDBUDPString = - Commons.Properties.Resources.AniDB_ResponseWait; + Resources.AniDB_ResponseWait; else - ServerInfo.Instance.WaitingOnResponseAniDBUDPString = Commons.Properties.Resources.Command_Idle; + ServerInfo.Instance.WaitingOnResponseAniDBUDPString = Resources.Command_Idle; if (isWaiting) WaitingOnResponseTime = DateTime.Now; @@ -328,12 +292,11 @@ public bool Login() else { curSessionID = login.SessionID; - encoding = login.Encoding; - this.isLoggedOn = true; - this.IsInvalidSession = false; + isLoggedOn = true; + IsInvalidSession = false; return true; } - + return false; } @@ -373,7 +336,7 @@ public Raw_AniDB_Episode GetEpisodeInfo(int episodeID) { try { - logger.Trace("ProcessResult_GetEpisodeInfo: {0}", getInfoCmd.EpisodeInfo.ToString()); + logger.Trace("ProcessResult_GetEpisodeInfo: {0}", getInfoCmd.EpisodeInfo); return getInfoCmd.EpisodeInfo; } catch (Exception ex) @@ -407,7 +370,7 @@ public Raw_AniDB_File GetFileInfo(IHash vidLocal) { try { - logger.Trace("ProcessResult_GetFileInfo: {0}", getInfoCmd.fileInfo.ToString()); + logger.Trace("ProcessResult_GetFileInfo: {0}", getInfoCmd.fileInfo); if (ServerSettings.AniDB_DownloadReleaseGroups) { @@ -495,12 +458,12 @@ public void UpdateMyListStats() } } - public bool GetUpdated(ref List updatedAnimeIDs, ref long startTime) + public void GetUpdated(ref List updatedAnimeIDs, ref long startTime) { //startTime = 0; updatedAnimeIDs = new List(); - if (!Login()) return false; + if (!Login()) return; lock (lockAniDBConnections) { @@ -511,16 +474,12 @@ public bool GetUpdated(ref List updatedAnimeIDs, ref long startTime) new UnicodeEncoding(true, false)); SetWaitingOnResponse(false); - if (ev == enHelperActivityType.GotUpdated && cmdUpdated != null && cmdUpdated.RecordCount > 0) + if (ev == enHelperActivityType.GotUpdated && cmdUpdated.RecordCount > 0) { startTime = long.Parse(cmdUpdated.StartTime); updatedAnimeIDs = cmdUpdated.AnimeIDList; - - return true; } } - - return false; } public void UpdateMyListFileStatus(IHash fileDataLocal, bool watched, DateTime? watchedDate) @@ -532,7 +491,7 @@ public void UpdateMyListFileStatus(IHash fileDataLocal, bool watched, DateTime? lock (lockAniDBConnections) { AniDBCommand_UpdateFile cmdUpdateFile = new AniDBCommand_UpdateFile(); - cmdUpdateFile.Init(fileDataLocal, watched, watchedDate, true, null); + cmdUpdateFile.Init(fileDataLocal, watched, watchedDate, true, ServerSettings.AniDB_MyList_StorageState); SetWaitingOnResponse(true); enHelperActivityType ev = cmdUpdateFile.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, new UnicodeEncoding(true, false)); @@ -544,8 +503,10 @@ public void UpdateMyListFileStatus(IHash fileDataLocal, bool watched, DateTime? cmdUpdateFile = new AniDBCommand_UpdateFile(); cmdUpdateFile.Init(fileDataLocal, watched, watchedDate, false, ServerSettings.AniDB_MyList_StorageState); - ev = cmdUpdateFile.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, + SetWaitingOnResponse(true); + cmdUpdateFile.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, new UnicodeEncoding(true, false)); + SetWaitingOnResponse(false); } } } @@ -576,20 +537,22 @@ public void UpdateMyListFileStatus(int animeID, int episodeNumber, bool watched) // we do this by issueing the same command without the edit flag cmdUpdateFile = new AniDBCommand_UpdateFile(); cmdUpdateFile.Init(animeID, episodeNumber, watched, false); - ev = cmdUpdateFile.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, + SetWaitingOnResponse(true); + cmdUpdateFile.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, new UnicodeEncoding(true, false)); + SetWaitingOnResponse(false); } } } - public bool AddFileToMyList(IHash fileDataLocal, ref DateTime? watchedDate) + public bool AddFileToMyList(IHash fileDataLocal, ref DateTime? watchedDate, ref AniDBFile_State? state) { if (!ServerSettings.AniDB_MyList_AddFiles) return false; if (!Login()) return false; - enHelperActivityType ev = enHelperActivityType.NoSuchMyListFile; - AniDBCommand_AddFile cmdAddFile = null; + enHelperActivityType ev; + AniDBCommand_AddFile cmdAddFile; lock (lockAniDBConnections) { @@ -601,10 +564,11 @@ public bool AddFileToMyList(IHash fileDataLocal, ref DateTime? watchedDate) SetWaitingOnResponse(false); } - // if the user already has this file on + // if the user already has this file on if (ev == enHelperActivityType.FileAlreadyExists && cmdAddFile.FileData != null) { watchedDate = cmdAddFile.WatchedDate; + state = cmdAddFile.State; return cmdAddFile.ReturnIsWatched; } @@ -615,8 +579,8 @@ public bool AddFileToMyList(IHash fileDataLocal, ref DateTime? watchedDate) { if (!Login()) return null; - enHelperActivityType ev = enHelperActivityType.NoSuchMyListFile; - AniDBCommand_AddFile cmdAddFile = null; + enHelperActivityType ev; + AniDBCommand_AddFile cmdAddFile; lock (lockAniDBConnections) { @@ -628,7 +592,7 @@ public bool AddFileToMyList(IHash fileDataLocal, ref DateTime? watchedDate) SetWaitingOnResponse(false); } - // if the user already has this file on + // if the user already has this file on if (ev == enHelperActivityType.FileAlreadyExists && cmdAddFile.FileData != null && ServerSettings.AniDB_MyList_ReadWatched) { watchedDate = cmdAddFile.WatchedDate; @@ -639,108 +603,83 @@ public bool AddFileToMyList(IHash fileDataLocal, ref DateTime? watchedDate) return null; } - internal bool MarkFileAsExternalStorage(string Hash, long FileSize) + internal void MarkFileAsExternalStorage(string Hash, long FileSize) { - if (!Login()) return false; - - enHelperActivityType ev = enHelperActivityType.NoSuchMyListFile; - AniDBCommand_MarkFileAsExternal cmdMarkFileExternal = null; + if (!Login()) return; lock (lockAniDBConnections) { - cmdMarkFileExternal = new AniDBCommand_MarkFileAsExternal(); + var cmdMarkFileExternal = new AniDBCommand_MarkFileAsExternal(); cmdMarkFileExternal.Init(Hash, FileSize); SetWaitingOnResponse(true); - ev = cmdMarkFileExternal.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, + cmdMarkFileExternal.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, new UnicodeEncoding(true, false)); SetWaitingOnResponse(false); } - - return true; } - internal bool MarkFileAsUnknown(string Hash, long FileSize) + internal void MarkFileAsUnknown(string Hash, long FileSize) { - if (!Login()) return false; - - enHelperActivityType ev = enHelperActivityType.NoSuchMyListFile; - AniDBCommand_MarkFileAsUnknown cmdMarkFileUnknown = null; + if (!Login()) return; lock (lockAniDBConnections) { - cmdMarkFileUnknown = new AniDBCommand_MarkFileAsUnknown(); + var cmdMarkFileUnknown = new AniDBCommand_MarkFileAsUnknown(); cmdMarkFileUnknown.Init(Hash, FileSize); SetWaitingOnResponse(true); - ev = cmdMarkFileUnknown.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, + cmdMarkFileUnknown.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, new UnicodeEncoding(true, false)); SetWaitingOnResponse(false); } - - return true; } - public bool MarkFileAsDeleted(string hash, long fileSize) + public void MarkFileAsDeleted(string hash, long fileSize) { - if (!Login()) return false; - - enHelperActivityType ev = enHelperActivityType.NoSuchMyListFile; - AniDBCommand_MarkFileAsDeleted cmdDelFile = null; + if (!Login()) return; lock (lockAniDBConnections) { - cmdDelFile = new AniDBCommand_MarkFileAsDeleted(); + var cmdDelFile = new AniDBCommand_MarkFileAsDeleted(); cmdDelFile.Init(hash, fileSize); SetWaitingOnResponse(true); - ev = cmdDelFile.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, + cmdDelFile.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, new UnicodeEncoding(true, false)); SetWaitingOnResponse(false); } - - return true; } - public bool DeleteFileFromMyList(string hash, long fileSize) + public void DeleteFileFromMyList(string hash, long fileSize) { - if (!ServerSettings.AniDB_MyList_AddFiles) return false; - - if (!Login()) return false; + if (!ServerSettings.AniDB_MyList_AddFiles) return; - enHelperActivityType ev = enHelperActivityType.NoSuchMyListFile; - AniDBCommand_DeleteFile cmdDelFile = null; + if (!Login()) return; lock (lockAniDBConnections) { - cmdDelFile = new AniDBCommand_DeleteFile(); + var cmdDelFile = new AniDBCommand_DeleteFile(); cmdDelFile.Init(hash, fileSize); SetWaitingOnResponse(true); - ev = cmdDelFile.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, + cmdDelFile.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, new UnicodeEncoding(true, false)); SetWaitingOnResponse(false); } - - return true; } - public bool DeleteFileFromMyList(int fileID) + public void DeleteFileFromMyList(int fileID) { - if (!ServerSettings.AniDB_MyList_AddFiles) return false; - - if (!Login()) return false; + if (!ServerSettings.AniDB_MyList_AddFiles) return; - enHelperActivityType ev = enHelperActivityType.NoSuchMyListFile; - AniDBCommand_DeleteFile cmdDelFile = null; + if (!Login()) return; lock (lockAniDBConnections) { - cmdDelFile = new AniDBCommand_DeleteFile(); + var cmdDelFile = new AniDBCommand_DeleteFile(); cmdDelFile.Init(fileID); SetWaitingOnResponse(true); - ev = cmdDelFile.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, + cmdDelFile.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, new UnicodeEncoding(true, false)); SetWaitingOnResponse(false); } - - return true; } public SVR_AniDB_Anime GetAnimeInfoUDP(int animeID, bool forceRefresh) @@ -758,12 +697,7 @@ public SVR_AniDB_Anime GetAnimeInfoUDP(int animeID, bool forceRefresh) //TODO: Skip AND null Anime? or Skip OR null Anime? if (skip) - { - if (anime == null) - anime = RepoFactory.AniDB_Anime.GetByAnimeID(animeID); - return anime; - } if (!Login()) return null; @@ -821,12 +755,12 @@ public AniDB_Character GetCharacterInfoUDP(int charID) return chr; } - public AniDB_ReleaseGroup GetReleaseGroupUDP(int groupID) + public void GetReleaseGroupUDP(int groupID) { - if (!Login()) return null; + if (!Login()) return; - enHelperActivityType ev = enHelperActivityType.NoSuchGroup; - AniDBCommand_GetGroup getCmd = null; + enHelperActivityType ev; + AniDBCommand_GetGroup getCmd; lock (lockAniDBConnections) { getCmd = new AniDBCommand_GetGroup(); @@ -836,25 +770,19 @@ public AniDB_ReleaseGroup GetReleaseGroupUDP(int groupID) SetWaitingOnResponse(false); } - AniDB_ReleaseGroup relGroup = null; - if (ev == enHelperActivityType.GotGroup && getCmd.Group != null) - { - relGroup = RepoFactory.AniDB_ReleaseGroup.GetByGroupID(groupID); - if (relGroup == null) relGroup = new AniDB_ReleaseGroup(); - - relGroup.Populate(getCmd.Group); - RepoFactory.AniDB_ReleaseGroup.Save(relGroup); - } + if (ev != enHelperActivityType.GotGroup || getCmd.Group == null) return; + var relGroup = RepoFactory.AniDB_ReleaseGroup.GetByGroupID(groupID) ?? new AniDB_ReleaseGroup(); - return relGroup; + relGroup.Populate(getCmd.Group); + RepoFactory.AniDB_ReleaseGroup.Save(relGroup); } public GroupStatusCollection GetReleaseGroupStatusUDP(int animeID) { if (!Login()) return null; - enHelperActivityType ev = enHelperActivityType.NoSuchCreator; - AniDBCommand_GetGroupStatus getCmd = null; + enHelperActivityType ev; + AniDBCommand_GetGroupStatus getCmd; lock (lockAniDBConnections) { getCmd = new AniDBCommand_GetGroupStatus(); @@ -864,57 +792,43 @@ public GroupStatusCollection GetReleaseGroupStatusUDP(int animeID) SetWaitingOnResponse(false); } - if (ev == enHelperActivityType.GotGroupStatus && getCmd.GrpStatusCollection != null) - { - // delete existing records - + if (ev != enHelperActivityType.GotGroupStatus || getCmd.GrpStatusCollection == null) + return getCmd.GrpStatusCollection; - RepoFactory.AniDB_GroupStatus.DeleteForAnime(animeID); - - // save the records - foreach (Raw_AniDB_GroupStatus raw in getCmd.GrpStatusCollection.Groups) - { - AniDB_GroupStatus grpstat = new AniDB_GroupStatus(); - grpstat.Populate(raw); - RepoFactory.AniDB_GroupStatus.Save(grpstat); - } - - // updated cached stats - // we don't do it in the save method as it would be too many unecessary updates - logger.Trace("Updating group stats by anime from GetReleaseGroupStatusUDP: {0}", animeID); - - - // StatsCache.Instance.UpdateUsingAnime(animeID); - //Removed QueueUpdateStatus will Update Groups + // delete existing records + RepoFactory.AniDB_GroupStatus.DeleteForAnime(animeID); + // save the records + foreach (Raw_AniDB_GroupStatus raw in getCmd.GrpStatusCollection.Groups) + { + AniDB_GroupStatus grpstat = new AniDB_GroupStatus(); + grpstat.Populate(raw); + RepoFactory.AniDB_GroupStatus.Save(grpstat); + } - if (getCmd.GrpStatusCollection.LatestEpisodeNumber > 0) + if (getCmd.GrpStatusCollection.LatestEpisodeNumber > 0) + { + // update the anime with a record of the latest subbed episode + SVR_AniDB_Anime anime = RepoFactory.AniDB_Anime.GetByAnimeID(animeID); + if (anime != null) { - // update the anime with a record of the latest subbed episode - SVR_AniDB_Anime anime = RepoFactory.AniDB_Anime.GetByAnimeID(animeID); - if (anime != null) + anime.LatestEpisodeNumber = getCmd.GrpStatusCollection.LatestEpisodeNumber; + RepoFactory.AniDB_Anime.Save(anime); + + // check if we have this episode in the database + // if not get it now by updating the anime record + List eps = RepoFactory.AniDB_Episode.GetByAnimeIDAndEpisodeNumber(animeID, + getCmd.GrpStatusCollection.LatestEpisodeNumber); + if (eps.Count == 0) { - anime.LatestEpisodeNumber = getCmd.GrpStatusCollection.LatestEpisodeNumber; - RepoFactory.AniDB_Anime.Save(anime); - - // check if we have this episode in the database - // if not get it now by updating the anime record - List eps = RepoFactory.AniDB_Episode.GetByAnimeIDAndEpisodeNumber(animeID, - getCmd.GrpStatusCollection.LatestEpisodeNumber); - if (eps.Count == 0) - { - CommandRequest_GetAnimeHTTP cr_anime = - new CommandRequest_GetAnimeHTTP(animeID, true, false); - cr_anime.Save(); - } - // update the missing episode stats on groups and children - SVR_AnimeSeries series = RepoFactory.AnimeSeries.GetByAnimeID(animeID); - if (series != null) - { - series.QueueUpdateStats(); - //series.TopLevelAnimeGroup.UpdateStatsFromTopLevel(true, true, true); - } + CommandRequest_GetAnimeHTTP cr_anime = + new CommandRequest_GetAnimeHTTP(animeID, true, false); + cr_anime.Save(); } + // update the missing episode stats on groups and children + SVR_AnimeSeries series = RepoFactory.AnimeSeries.GetByAnimeID(animeID); + series?.QueueUpdateStats(); + //series.TopLevelAnimeGroup.UpdateStatsFromTopLevel(true, true, true); } } @@ -971,39 +885,28 @@ public AniDB_Review GetReviewUDP(int reviewID) return review; } - public bool VoteAnime(int animeID, decimal voteValue, AniDBVoteType voteType) + public void VoteAnime(int animeID, decimal voteValue, AniDBVoteType voteType) { - if (!Login()) return false; - - enHelperActivityType ev = enHelperActivityType.NoSuchVote; - AniDBCommand_Vote cmdVote = null; + if (!Login()) return; lock (lockAniDBConnections) { - cmdVote = new AniDBCommand_Vote(); + var cmdVote = new AniDBCommand_Vote(); cmdVote.Init(animeID, voteValue, voteType); SetWaitingOnResponse(true); - ev = cmdVote.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, new UnicodeEncoding(true, false)); + var ev = cmdVote.Process(ref soUdp, ref remoteIpEndPoint, curSessionID, new UnicodeEncoding(true, false)); SetWaitingOnResponse(false); - if (ev == enHelperActivityType.Voted || ev == enHelperActivityType.VoteUpdated) + if (ev != enHelperActivityType.Voted && ev != enHelperActivityType.VoteUpdated) return; + AniDB_Vote thisVote = RepoFactory.AniDB_Vote.GetByEntityAndType(cmdVote.EntityID, voteType) ?? new AniDB_Vote { - AniDB_Vote thisVote = RepoFactory.AniDB_Vote.GetByEntityAndType(cmdVote.EntityID, voteType); + EntityID = cmdVote.EntityID + }; - if (thisVote == null) - { - thisVote = new AniDB_Vote - { - EntityID = cmdVote.EntityID - }; - } - thisVote.VoteType = (int) cmdVote.VoteType; - thisVote.VoteValue = cmdVote.VoteValue; - RepoFactory.AniDB_Vote.Save(thisVote); - } + thisVote.VoteType = (int) cmdVote.VoteType; + thisVote.VoteValue = cmdVote.VoteValue; + RepoFactory.AniDB_Vote.Save(thisVote); } - - return false; } public void VoteAnimeRevoke(int animeID, AniDBVoteType voteType) @@ -1025,10 +928,9 @@ public SVR_AniDB_Anime GetAnimeInfoHTTP(ISession session, int animeID, bool forc { //if (!Login()) return null; - SVR_AniDB_Anime anime; ISessionWrapper sessionWrapper = session.Wrap(); - anime = RepoFactory.AniDB_Anime.GetByAnimeID(sessionWrapper, animeID); + var anime = RepoFactory.AniDB_Anime.GetByAnimeID(sessionWrapper, animeID); bool skip = true; bool animeRecentlyUpdated = false; if (anime != null) @@ -1056,7 +958,10 @@ public SVR_AniDB_Anime GetAnimeInfoHTTP(ISession session, int animeID, bool forc getAnimeCmd.Init(animeID, false, forceRefresh, false); var result = getAnimeCmd.Process(); if (result == enHelperActivityType.Banned_555 || result == enHelperActivityType.NoSuchAnime) + { + logger.Error($"Failed get anime info for {animeID}. AniDB ban or No Such Anime returned"); return null; + } } @@ -1072,22 +977,26 @@ public SVR_AniDB_Anime GetAnimeInfoHTTP(ISession session, int animeID, bool forc }*/ } + logger.Error($"Failed get anime info for {animeID}. Anime was null"); return null; } private SVR_AniDB_Anime SaveResultsForAnimeXML(ISession session, int animeID, bool downloadRelations, AniDBHTTPCommand_GetFullAnime getAnimeCmd) { - SVR_AniDB_Anime anime = null; ISessionWrapper sessionWrapper = session.Wrap(); logger.Trace("cmdResult.Anime: {0}", getAnimeCmd.Anime); - anime = RepoFactory.AniDB_Anime.GetByAnimeID(sessionWrapper, animeID) ?? new SVR_AniDB_Anime(); - anime.PopulateAndSaveFromHTTP(session, getAnimeCmd.Anime, getAnimeCmd.Episodes, getAnimeCmd.Titles, + var anime = RepoFactory.AniDB_Anime.GetByAnimeID(sessionWrapper, animeID) ?? new SVR_AniDB_Anime(); + if (!anime.PopulateAndSaveFromHTTP(session, getAnimeCmd.Anime, getAnimeCmd.Episodes, getAnimeCmd.Titles, getAnimeCmd.Categories, getAnimeCmd.Tags, getAnimeCmd.Characters, getAnimeCmd.Relations, getAnimeCmd.SimilarAnime, getAnimeCmd.Recommendations, - downloadRelations); + downloadRelations)) + { + logger.Error($"Failed populate anime info for {animeID}"); + return null; + } // Request an image download CommandRequest_DownloadImage cmd = new CommandRequest_DownloadImage(anime.AnimeID, @@ -1144,9 +1053,9 @@ private SVR_AniDB_Anime SaveResultsForAnimeXML(ISession session, int animeID, bo public bool ValidAniDBCredentials() { - if (string.IsNullOrEmpty(this.userName) || string.IsNullOrEmpty(this.password) || - string.IsNullOrEmpty(this.serverName) - || string.IsNullOrEmpty(this.serverPort) || string.IsNullOrEmpty(this.clientPort)) + if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password) || + string.IsNullOrEmpty(serverName) + || string.IsNullOrEmpty(serverPort) || string.IsNullOrEmpty(clientPort)) { //OnAniDBStatusEvent(new AniDBStatusEventArgs(enHelperActivityType.OtherError, "ERROR: Please enter valid AniDB credentials via Configuration first")); return false; @@ -1162,7 +1071,7 @@ private bool BindToLocalPort() localIpEndPoint = null; // Dont send Expect 100 requests. These requests arnt always supported by remote internet devices, in which case can cause failure. - System.Net.ServicePointManager.Expect100Continue = false; + ServicePointManager.Expect100Continue = false; try { @@ -1177,7 +1086,7 @@ private bool BindToLocalPort() soUdp.ReceiveTimeout = 30000; // 30 seconds logger.Info("BindToLocalPort: Bound to local address: {0} - Port: {1} ({2})", - localIpEndPoint.ToString(), + localIpEndPoint, clientPort, localIpEndPoint.AddressFamily); @@ -1201,9 +1110,9 @@ private bool BindToRemotePort() IPHostEntry remoteHostEntry = Dns.GetHostEntry(serverName); remoteIpEndPoint = new IPEndPoint(remoteHostEntry.AddressList[0], Convert.ToInt32(serverPort)); - logger.Info("BindToRemotePort: Bound to remote address: " + remoteIpEndPoint.Address.ToString() + + logger.Info("BindToRemotePort: Bound to remote address: " + remoteIpEndPoint.Address + " : " + - remoteIpEndPoint.Port.ToString()); + remoteIpEndPoint.Port); return true; } diff --git a/Shoko.Server/AniDB_API/AniDBImageRateLimiter.cs b/Shoko.Server/AniDB_API/AniDBImageRateLimiter.cs new file mode 100644 index 000000000..00982362d --- /dev/null +++ b/Shoko.Server/AniDB_API/AniDBImageRateLimiter.cs @@ -0,0 +1,77 @@ +using System.Diagnostics; +using System.Threading; +using NLog; + +namespace Shoko.Server.AniDB_API +{ + public sealed class AniDbImageRateLimiter + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static readonly AniDbImageRateLimiter instance = new AniDbImageRateLimiter(); + + // Short Term rate + private static int ShortDelay = 500; + + // Long Term rate + private static int LongDelay = 1000; + + // Switch to longer delay after 1 hour + private static long shortPeriod = 60 * 60 * 1000; + + // Switch to shorter delay after 30 minutes of inactivity + private static long resetPeriod = 30 * 60 * 1000; + + private static Stopwatch _requestWatch = new Stopwatch(); + + private static Stopwatch _activeTimeWatch = new Stopwatch(); + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static AniDbImageRateLimiter() + { + _requestWatch.Start(); + _activeTimeWatch.Start(); + } + + public static AniDbImageRateLimiter Instance => instance; + + private AniDbImageRateLimiter() + { + } + + public void ResetRate() + { + long elapsedTime = _activeTimeWatch.ElapsedMilliseconds; + _activeTimeWatch.Restart(); + Logger.Trace($"Rate is reset. Active time was {elapsedTime} ms."); + } + + public void EnsureRate() + { + lock (instance) + { + long delay = _requestWatch.ElapsedMilliseconds; + + if (delay > resetPeriod) + { + ResetRate(); + } + + int currentDelay = _activeTimeWatch.ElapsedMilliseconds > shortPeriod ? LongDelay : ShortDelay; + + if (delay > currentDelay) + { + Logger.Trace($"Time since last request is {delay} ms, not throttling."); + _requestWatch.Restart(); + return; + } + + Logger.Trace($"Time since last request is {delay} ms, throttling for {currentDelay}."); + Thread.Sleep(currentDelay); + + Logger.Trace("Sending AniDB command."); + _requestWatch.Restart(); + } + } + } +} \ No newline at end of file diff --git a/Shoko.Server/AniDB_API/Commands/AniDBCommand_AddFile.cs b/Shoko.Server/AniDB_API/Commands/AniDBCommand_AddFile.cs index 46ada26a1..cbe00cc8c 100644 --- a/Shoko.Server/AniDB_API/Commands/AniDBCommand_AddFile.cs +++ b/Shoko.Server/AniDB_API/Commands/AniDBCommand_AddFile.cs @@ -11,6 +11,7 @@ public class AniDBCommand_AddFile : AniDBUDPCommand, IAniDBUDPCommand public IHash FileData = null; public bool ReturnIsWatched = false; public DateTime? WatchedDate = null; + public AniDBFile_State? State = null; public string GetKey() { @@ -46,13 +47,23 @@ public virtual enHelperActivityType Process(ref Socket soUDP, if (arrResult.Length >= 2) { string[] arrStatus = arrResult[1].Split('|'); + int state = int.Parse(arrStatus[6]); + State = (AniDBFile_State) state; + int viewdate = int.Parse(arrStatus[7]); ReturnIsWatched = viewdate > 0; - DateTime utcDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - utcDate = utcDate.AddSeconds(viewdate); + if (ReturnIsWatched) + { + DateTime utcDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + utcDate = utcDate.AddSeconds(viewdate); - WatchedDate = utcDate.ToLocalTime(); + WatchedDate = utcDate.ToLocalTime(); + } + else + { + WatchedDate = null; + } } } return enHelperActivityType.FileAlreadyExists; diff --git a/Shoko.Server/AniDB_API/Commands/AniDBCommand_UpdateFile.cs b/Shoko.Server/AniDB_API/Commands/AniDBCommand_UpdateFile.cs index a12f87522..e861e7758 100644 --- a/Shoko.Server/AniDB_API/Commands/AniDBCommand_UpdateFile.cs +++ b/Shoko.Server/AniDB_API/Commands/AniDBCommand_UpdateFile.cs @@ -2,6 +2,7 @@ using System.Net; using System.Net.Sockets; using System.Text; +using Shoko.Commons.Utils; using Shoko.Models.Interfaces; using Shoko.Server; @@ -9,8 +10,8 @@ namespace AniDBAPI.Commands { public class AniDBCommand_UpdateFile : AniDBUDPCommand, IAniDBUDPCommand { - public IHash FileData = null; - public bool IsWatched = false; + public IHash FileData; + public bool IsWatched; public string GetKey() { @@ -57,19 +58,6 @@ public AniDBCommand_UpdateFile() commandType = enAniDBCommandType.UpdateFile; } - /*public void Init(IHash fileData, bool watched) - { - FileData = fileData; - IsWatched = watched; - - commandID = fileData.Info; - - commandText = "MYLISTADD size=" + fileData.FileSize.ToString(); - commandText += "&ed2k=" + fileData.ED2KHash; - commandText += "&viewed=" + (IsWatched ? "1" : "0"); //viewed - commandText += "&edit=1"; - }*/ - public void Init(IHash fileData, bool watched, DateTime? watchedDate, bool isEdit, AniDBFile_State? fileState) { FileData = fileData; @@ -77,14 +65,13 @@ public void Init(IHash fileData, bool watched, DateTime? watchedDate, bool isEdi commandID = fileData.Info; - commandText = "MYLISTADD size=" + fileData.FileSize.ToString(); + commandText = "MYLISTADD size=" + fileData.FileSize; commandText += "&ed2k=" + fileData.ED2KHash; commandText += "&viewed=" + (IsWatched ? "1" : "0"); //viewed if (fileState.HasValue) commandText += "&state=" + (int) fileState; if (watchedDate.HasValue) - commandText += "&viewdate=" + Shoko.Commons.Utils.AniDB.GetAniDBDateAsSeconds(watchedDate.Value) - .ToString(); + commandText += "&viewdate=" + AniDB.GetAniDBDateAsSeconds(watchedDate.Value); if (isEdit) commandText += "&edit=1"; } @@ -93,9 +80,9 @@ public void Init(int animeID, int episodeNumber, bool watched, bool isEdit) { IsWatched = watched; - commandText = "MYLISTADD aid=" + animeID.ToString(); + commandText = "MYLISTADD aid=" + animeID; commandText += "&generic=1"; - commandText += "&epno=" + episodeNumber.ToString(); + commandText += "&epno=" + episodeNumber; commandText += "&state=" + (int) ServerSettings.AniDB_MyList_StorageState; commandText += "&viewed=" + (IsWatched ? "1" : "0"); //viewed if (isEdit) diff --git a/Shoko.Server/Commands/AniDB/CommandRequest_AddFileToMyList.cs b/Shoko.Server/Commands/AniDB/CommandRequest_AddFileToMyList.cs index 0c79ba13a..ba07fbef8 100644 --- a/Shoko.Server/Commands/AniDB/CommandRequest_AddFileToMyList.cs +++ b/Shoko.Server/Commands/AniDB/CommandRequest_AddFileToMyList.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Xml; +using AniDBAPI; using Shoko.Commons.Queue; using Shoko.Models.Queue; using Shoko.Models.Server; @@ -73,48 +74,49 @@ public override void ProcessCommand() // mark the video file as watched DateTime? watchedDate = null; bool? newWatchedStatus; + AniDBFile_State? state = null; if (isManualLink) newWatchedStatus = ShokoService.AnidbProcessor.AddFileToMyList(xrefs[0].AnimeID, xrefs[0].GetEpisode().EpisodeNumber, ref watchedDate); else - newWatchedStatus = ShokoService.AnidbProcessor.AddFileToMyList(vid, ref watchedDate); + newWatchedStatus = ShokoService.AnidbProcessor.AddFileToMyList(vid, ref watchedDate, ref state); // do for all AniDB users List aniDBUsers = RepoFactory.JMMUser.GetAniDBUsers(); - if (aniDBUsers.Count > 0 && newWatchedStatus != null) + if (aniDBUsers.Count > 0) { + string datemessage = watchedDate?.ToShortDateString() ?? "Not Watched"; + if (watchedDate?.Equals(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToLocalTime()) ?? false) + datemessage = "No Watch Date Specified"; + logger.Info($"Adding file to list: {vid.FileName} - {datemessage}"); + bool watched = watchedDate != null; + if (newWatchedStatus != null) watched = newWatchedStatus.Value; + SVR_JMMUser juser = aniDBUsers[0]; - vid.ToggleWatchedStatus(newWatchedStatus.Value, false, watchedDate, false, false, juser.JMMUserID, - false, true); - logger.Info($"Adding file to list: {vid.FileName} - {watchedDate}"); + bool watchedLocally = vid.GetUserRecord(juser.JMMUserID)?.WatchedDate != null; + bool watchedChanged = watched != watchedLocally; + + // handle import watched settings. Don't update AniDB in either case, we'll do that with the storage state + if (ServerSettings.AniDB_MyList_ReadWatched && watched && !watchedLocally) + { + vid.ToggleWatchedStatus(true, false, watchedDate, false, false, juser.JMMUserID, + false, false); + } + else if (ServerSettings.AniDB_MyList_ReadUnwatched && !watched && watchedLocally) + { + vid.ToggleWatchedStatus(false, false, watchedDate, false, false, juser.JMMUserID, + false, false); + } - // if the the episode is watched we may want to set the file to watched as well - if (ServerSettings.Import_UseExistingFileWatchedStatus && !newWatchedStatus.Value) + if (watchedChanged || state != ServerSettings.AniDB_MyList_StorageState) { - if (animeEpisodes.Count > 0) - { - SVR_AnimeEpisode ep = animeEpisodes[0]; - SVR_AnimeEpisode_User epUser = null; - - foreach (SVR_JMMUser tempuser in aniDBUsers) - { - // only find the first user who watched this - if (epUser == null) - epUser = ep.GetUserRecord(tempuser.JMMUserID); - } - - if (epUser != null) - { - logger.Info( - $"Setting file as watched, because episode was already watched: {vid.FileName} - user: {epUser.JMMUserID}"); - vid.ToggleWatchedStatus(true, true, epUser.WatchedDate, false, false, - epUser.JMMUserID, false, true); - } - } + int watchedDateSec = Commons.Utils.AniDB.GetAniDBDateAsSeconds(watchedDate); + var cmdUpdate = new CommandRequest_UpdateMyListFileStatus(Hash, watched, false, watchedDateSec); + cmdUpdate.Save(); } } diff --git a/Shoko.Server/Commands/AniDB/CommandRequest_GetFile.cs b/Shoko.Server/Commands/AniDB/CommandRequest_GetFile.cs index 04acdfca8..6d972f282 100644 --- a/Shoko.Server/Commands/AniDB/CommandRequest_GetFile.cs +++ b/Shoko.Server/Commands/AniDB/CommandRequest_GetFile.cs @@ -127,13 +127,7 @@ public override void ProcessCommand() } } SVR_AniDB_Anime anime = RepoFactory.AniDB_Anime.GetByAnimeID(aniFile.AnimeID); - if (anime != null) - { - using (var session = DatabaseFactory.SessionFactory.OpenSession()) - { - RepoFactory.AniDB_Anime.Save(anime); - } - } + if (anime != null) RepoFactory.AniDB_Anime.Save(anime); SVR_AnimeSeries series = RepoFactory.AnimeSeries.GetByAnimeID(aniFile.AnimeID); series.UpdateStats(false, true, true); // StatsCache.Instance.UpdateUsingAniDBFile(vlocal.Hash); diff --git a/Shoko.Server/Commands/AniDB/CommandRequest_SyncMyList.cs b/Shoko.Server/Commands/AniDB/CommandRequest_SyncMyList.cs index 95b2f88f1..e7a6d877b 100644 --- a/Shoko.Server/Commands/AniDB/CommandRequest_SyncMyList.cs +++ b/Shoko.Server/Commands/AniDB/CommandRequest_SyncMyList.cs @@ -81,36 +81,36 @@ public override void ProcessCommand() double pct = 0; // Add missing files on AniDB - Dictionary onlineFiles = new Dictionary(); - foreach (Raw_AniDB_MyListFile myitem in cmd.MyListItems) - onlineFiles[myitem.FileID] = myitem; - - Dictionary dictAniFiles = new Dictionary(); - IReadOnlyList allAniFiles = RepoFactory.AniDB_File.GetAll(); - foreach (SVR_AniDB_File anifile in allAniFiles) - dictAniFiles[anifile.Hash] = anifile; + var onlineFiles = cmd.MyListItems.ToLookup(a => a.FileID); + var dictAniFiles = RepoFactory.AniDB_File.GetAll().ToLookup(a => a.Hash); int missingFiles = 0; foreach (SVR_VideoLocal vid in RepoFactory.VideoLocal.GetAll() .Where(a => !string.IsNullOrEmpty(a.Hash)).ToList()) { // Does it have a linked AniFile - if (!dictAniFiles.ContainsKey(vid.Hash)) continue; + if (!dictAniFiles.Contains(vid.Hash)) continue; - int fileID = dictAniFiles[vid.Hash].FileID; + int fileID = dictAniFiles[vid.Hash].FirstOrDefault()?.FileID ?? 0; + if (fileID == 0) continue; // Is it in MyList - if (onlineFiles.ContainsKey(fileID)) + if (onlineFiles.Contains(fileID)) { - Raw_AniDB_MyListFile file = onlineFiles[fileID]; + Raw_AniDB_MyListFile file = onlineFiles[fileID].FirstOrDefault(a => a != null); // Update file state if deleted - if (file.State == (int) AniDBFile_State.Deleted) + if (file != null && file.State != (int) ServerSettings.AniDB_MyList_StorageState) { + int seconds = Commons.Utils.AniDB.GetAniDBDateAsSeconds(file.WatchedDate); CommandRequest_UpdateMyListFileStatus cmdUpdateFile = - new CommandRequest_UpdateMyListFileStatus(vid.Hash, file.WatchedDate.HasValue, false, 0); + new CommandRequest_UpdateMyListFileStatus(vid.Hash, file.WatchedDate.HasValue, false, + seconds); cmdUpdateFile.Save(); } - continue; + else if (file != null) + { + continue; + } } // means we have found a file in our local collection, which is not recorded online @@ -130,16 +130,9 @@ public override void ProcessCommand() List filesToRemove = new List(); foreach (Raw_AniDB_MyListFile myitem in cmd.MyListItems) { - // ignore files mark as deleted by the user - if (myitem.State == (int) AniDBFile_State.Deleted) continue; - totalItems++; if (myitem.IsWatched) watchedItems++; - //calculate percentage - pct = totalItems / (double)cmd.MyListItems.Count * 100; - string spct = pct.ToString("#0.0"); - string hash = string.Empty; SVR_AniDB_File anifile = RepoFactory.AniDB_File.GetByFileID(myitem.FileID); diff --git a/Shoko.Server/Commands/CommandProcessorGeneral.cs b/Shoko.Server/Commands/CommandProcessorGeneral.cs old mode 100644 new mode 100755 index 9eeee181f..a779b7a0d --- a/Shoko.Server/Commands/CommandProcessorGeneral.cs +++ b/Shoko.Server/Commands/CommandProcessorGeneral.cs @@ -1,274 +1,274 @@ -using System; -using System.ComponentModel; -using System.Globalization; -using System.Threading; -using Shoko.Server.Repositories.Direct; -using NLog; -using Shoko.Commons.Queue; -using Shoko.Models.Queue; -using Shoko.Models.Server; -using Shoko.Server.Repositories; - -namespace Shoko.Server.Commands -{ - public class CommandProcessorGeneral - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - private BackgroundWorker workerCommands = new BackgroundWorker(); - private bool processingCommands = false; - private DateTime? pauseTime = null; - - - private object lockQueueCount = new object(); - private object lockQueueState = new object(); - private object lockPaused = new object(); - - public delegate void QueueCountChangedHandler(QueueCountEventArgs ev); - - public event QueueCountChangedHandler OnQueueCountChangedEvent; - - public delegate void QueueStateChangedHandler(QueueStateEventArgs ev); - - public event QueueStateChangedHandler OnQueueStateChangedEvent; - - private bool paused = false; - - public bool Paused - { - get - { - lock (lockPaused) - { - return paused; - } - } - set - { - lock (lockPaused) - { - paused = value; - if (paused) - { - QueueState = new QueueStateStruct - { - queueState = QueueStateEnum.Paused, - extraParams = new string[0] - }; - pauseTime = DateTime.Now; - } - else - { - QueueState = new QueueStateStruct - { - queueState = QueueStateEnum.Idle, - extraParams = new string[0] - }; - pauseTime = null; - ShokoService.AnidbProcessor.IsBanned = false; - ShokoService.AnidbProcessor.BanOrigin = string.Empty; - } - ServerInfo.Instance.GeneralQueuePaused = paused; - ServerInfo.Instance.GeneralQueueRunning = !paused; - } - } - } - - private int queueCount = 0; - - public int QueueCount - { - get - { - lock (lockQueueCount) - { - return queueCount; - } - } - set - { - lock (lockQueueCount) - { - queueCount = value; - } - OnQueueCountChangedEvent?.Invoke(new QueueCountEventArgs(queueCount)); - } - } - - private QueueStateStruct queueState = - new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; - - public QueueStateStruct QueueState - { - get - { - lock (lockQueueState) - { - return queueState; - } - } - set - { - lock (lockQueueState) - { - queueState = value; - } - OnQueueStateChangedEvent?.Invoke(new QueueStateEventArgs(queueState)); - } - } - - public bool ProcessingCommands - { - get { return processingCommands; } - } - - public CommandProcessorGeneral() - { - Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); - - workerCommands.WorkerReportsProgress = true; - workerCommands.WorkerSupportsCancellation = true; - workerCommands.DoWork += new DoWorkEventHandler(WorkerCommands_DoWork); - workerCommands.RunWorkerCompleted += new RunWorkerCompletedEventHandler(WorkerCommands_RunWorkerCompleted); - } - - void WorkerCommands_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) - { - Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); - - processingCommands = false; - paused = false; - - //logger.Trace("Stopping command worker..."); - QueueState = new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; - - QueueCount = 0; - } - - public void Init() - { - Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); - - processingCommands = true; - //logger.Trace("Starting command worker..."); - QueueState = new QueueStateStruct() - { - queueState = QueueStateEnum.StartingGeneral, - extraParams = new string[0] - }; - this.workerCommands.RunWorkerAsync(); - } - - public void Stop() - { - workerCommands.CancelAsync(); - } - - /// - /// This is simply used to tell the command processor that a new command has been added to the database - /// - public void NotifyOfNewCommand() - { - // if the worker is busy, it will pick up the next command from the DB - // do not pick new command if cancellation is requested - if (processingCommands || workerCommands.CancellationPending) - { - //logger.Trace("NotifyOfNewCommand exiting, worker already busy"); - return; - } - - // otherwise need to start the worker again - //logger.Trace("Restarting command worker..."); - - processingCommands = true; - if (!workerCommands.IsBusy) - this.workerCommands.RunWorkerAsync(); - } - - void WorkerCommands_DoWork(object sender, DoWorkEventArgs e) - { - while (true) - { - if (workerCommands.CancellationPending) - { - e.Cancel = true; - return; - } - - // if paused we will sleep for 5 seconds, and the try again - // we will remove the pause if it was set more than 12 hours ago - // the pause is initiated when banned from AniDB or manually by the user - if (Paused) - { - try - { - if (workerCommands.CancellationPending) - { - e.Cancel = true; - return; - } - - //logger.Trace("Queue is paused: {0}", pauseTime.Value); - TimeSpan ts = DateTime.Now - pauseTime.Value; - if (ts.TotalHours >= 12) - { - Paused = false; - } - } - catch - { - } - Thread.Sleep(200); - continue; - } - - - //logger.Trace("Looking for next command request..."); - - CommandRequest crdb = RepoFactory.CommandRequest.GetNextDBCommandRequestGeneral(); - if (crdb == null) return; - - if (workerCommands.CancellationPending) - { - e.Cancel = true; - return; - } - - QueueCount = RepoFactory.CommandRequest.GetQueuedCommandCountGeneral(); - //logger.Trace("{0} commands remaining in queue", QueueCount); - - //logger.Trace("Next command request: {0}", crdb.CommandID); - - ICommandRequest icr = CommandHelper.GetCommand(crdb); - if (icr == null) - { - logger.Error("No implementation found for command: {0}-{1}", crdb.CommandType, crdb.CommandID); - } - else - { - QueueState = icr.PrettyDescription; - - if (workerCommands.CancellationPending) - { - e.Cancel = true; - return; - } - - logger.Trace("Processing command request: {0}", crdb.CommandID); - try - { - icr.ProcessCommand(); - } - catch (Exception ex) - { - logger.Error(ex, "ProcessCommand exception: {0}\n{1}", crdb.CommandID, ex.ToString()); - } - } - - logger.Trace("Deleting command request: {0}", crdb.CommandID); - RepoFactory.CommandRequest.Delete(crdb.CommandRequestID); - - QueueCount = RepoFactory.CommandRequest.GetQueuedCommandCountGeneral(); - } - } - } +using System; +using System.ComponentModel; +using System.Globalization; +using System.Threading; +using Shoko.Server.Repositories.Direct; +using NLog; +using Shoko.Commons.Queue; +using Shoko.Models.Queue; +using Shoko.Models.Server; +using Shoko.Server.Repositories; + +namespace Shoko.Server.Commands +{ + public class CommandProcessorGeneral + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + private BackgroundWorker workerCommands = new BackgroundWorker(); + private bool processingCommands = false; + private DateTime? pauseTime = null; + + + private object lockQueueCount = new object(); + private object lockQueueState = new object(); + private object lockPaused = new object(); + + public delegate void QueueCountChangedHandler(QueueCountEventArgs ev); + + public event QueueCountChangedHandler OnQueueCountChangedEvent; + + public delegate void QueueStateChangedHandler(QueueStateEventArgs ev); + + public event QueueStateChangedHandler OnQueueStateChangedEvent; + + private bool paused = false; + + public bool Paused + { + get + { + lock (lockPaused) + { + return paused; + } + } + set + { + lock (lockPaused) + { + paused = value; + if (paused) + { + QueueState = new QueueStateStruct + { + queueState = QueueStateEnum.Paused, + extraParams = new string[0] + }; + pauseTime = DateTime.Now; + } + else + { + QueueState = new QueueStateStruct + { + queueState = QueueStateEnum.Idle, + extraParams = new string[0] + }; + pauseTime = null; + ShokoService.AnidbProcessor.IsBanned = false; + ShokoService.AnidbProcessor.BanOrigin = string.Empty; + } + ServerInfo.Instance.GeneralQueuePaused = paused; + ServerInfo.Instance.GeneralQueueRunning = !paused; + } + } + } + + private int queueCount = 0; + + public int QueueCount + { + get + { + lock (lockQueueCount) + { + return queueCount; + } + } + set + { + lock (lockQueueCount) + { + queueCount = value; + } + OnQueueCountChangedEvent?.Invoke(new QueueCountEventArgs(queueCount)); + } + } + + private QueueStateStruct queueState = + new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; + + public QueueStateStruct QueueState + { + get + { + lock (lockQueueState) + { + return queueState; + } + } + set + { + lock (lockQueueState) + { + queueState = value; + } + OnQueueStateChangedEvent?.Invoke(new QueueStateEventArgs(queueState)); + } + } + + public bool ProcessingCommands + { + get { return processingCommands; } + } + + public CommandProcessorGeneral() + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); + + workerCommands.WorkerReportsProgress = true; + workerCommands.WorkerSupportsCancellation = true; + workerCommands.DoWork += new DoWorkEventHandler(WorkerCommands_DoWork); + workerCommands.RunWorkerCompleted += new RunWorkerCompletedEventHandler(WorkerCommands_RunWorkerCompleted); + } + + void WorkerCommands_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); + + processingCommands = false; + paused = false; + + //logger.Trace("Stopping command worker..."); + QueueState = new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; + + QueueCount = 0; + } + + public void Init() + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); + + processingCommands = true; + //logger.Trace("Starting command worker..."); + QueueState = new QueueStateStruct() + { + queueState = QueueStateEnum.StartingGeneral, + extraParams = new string[0] + }; + this.workerCommands.RunWorkerAsync(); + } + + public void Stop() + { + workerCommands.CancelAsync(); + } + + /// + /// This is simply used to tell the command processor that a new command has been added to the database + /// + public void NotifyOfNewCommand() + { + // if the worker is busy, it will pick up the next command from the DB + // do not pick new command if cancellation is requested + if (processingCommands || workerCommands.CancellationPending) + { + //logger.Trace("NotifyOfNewCommand exiting, worker already busy"); + return; + } + + // otherwise need to start the worker again + //logger.Trace("Restarting command worker..."); + + processingCommands = true; + if (!workerCommands.IsBusy) + this.workerCommands.RunWorkerAsync(); + } + + void WorkerCommands_DoWork(object sender, DoWorkEventArgs e) + { + while (true) + { + if (workerCommands.CancellationPending) + { + e.Cancel = true; + return; + } + + // if paused we will sleep for 5 seconds, and the try again + // we will remove the pause if it was set more than 12 hours ago + // the pause is initiated when banned from AniDB or manually by the user + if (Paused) + { + try + { + if (workerCommands.CancellationPending) + { + e.Cancel = true; + return; + } + + //logger.Trace("Queue is paused: {0}", pauseTime.Value); + TimeSpan ts = DateTime.Now - pauseTime.Value; + if (ts.TotalHours >= 12) + { + Paused = false; + } + } + catch + { + } + Thread.Sleep(200); + continue; + } + + + //logger.Trace("Looking for next command request..."); + + CommandRequest crdb = RepoFactory.CommandRequest.GetNextDBCommandRequestGeneral(); + if (crdb == null) return; + + if (workerCommands.CancellationPending) + { + e.Cancel = true; + return; + } + + QueueCount = RepoFactory.CommandRequest.GetQueuedCommandCountGeneral(); + //logger.Trace("{0} commands remaining in queue", QueueCount); + + //logger.Trace("Next command request: {0}", crdb.CommandID); + + ICommandRequest icr = CommandHelper.GetCommand(crdb); + if (icr == null) + { + logger.Error("No implementation found for command: {0}-{1}", crdb.CommandType, crdb.CommandID); + } + else + { + QueueState = icr.PrettyDescription; + + if (workerCommands.CancellationPending) + { + e.Cancel = true; + return; + } + + logger.Trace("Processing command request: {0}", crdb.CommandID); + try + { + icr.ProcessCommand(); + } + catch (Exception ex) + { + logger.Error(ex, "ProcessCommand exception: {0}\n{1}", crdb.CommandID, ex.ToString()); + } + } + + logger.Trace("Deleting command request: {0}", crdb.CommandID); + RepoFactory.CommandRequest.Delete(crdb.CommandRequestID); + + QueueCount = RepoFactory.CommandRequest.GetQueuedCommandCountGeneral(); + } + } + } } \ No newline at end of file diff --git a/Shoko.Server/Commands/CommandProcessorHasher.cs b/Shoko.Server/Commands/CommandProcessorHasher.cs old mode 100644 new mode 100755 index ae69aef67..676f42b77 --- a/Shoko.Server/Commands/CommandProcessorHasher.cs +++ b/Shoko.Server/Commands/CommandProcessorHasher.cs @@ -1,254 +1,254 @@ -using System; -using System.ComponentModel; -using System.Globalization; -using System.Threading; -using Shoko.Server.Repositories.Direct; -using NLog; -using Shoko.Commons.Queue; -using Shoko.Models.Queue; -using Shoko.Models.Server; -using Shoko.Server.Repositories; - -namespace Shoko.Server.Commands -{ - public class CommandProcessorHasher - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - private BackgroundWorker workerCommands = new BackgroundWorker(); - private bool processingCommands = false; - private DateTime? pauseTime = null; - - private object lockQueueCount = new object(); - private object lockQueueState = new object(); - private object lockPaused = new object(); - - public delegate void QueueCountChangedHandler(QueueCountEventArgs ev); - - public event QueueCountChangedHandler OnQueueCountChangedEvent; - - public delegate void QueueStateChangedHandler(QueueStateEventArgs ev); - - public event QueueStateChangedHandler OnQueueStateChangedEvent; - - private bool paused = false; - - public bool Paused - { - get - { - lock (lockPaused) - { - return paused; - } - } - set - { - lock (lockPaused) - { - Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); - - paused = value; - if (paused) - { - QueueState = - new QueueStateStruct() {queueState = QueueStateEnum.Paused, extraParams = new string[0]}; - pauseTime = DateTime.Now; - } - else - { - QueueState = - new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; - pauseTime = null; - } - - ServerInfo.Instance.HasherQueuePaused = paused; - ServerInfo.Instance.HasherQueueRunning = !paused; - } - } - } - - private int queueCount = 0; - - public int QueueCount - { - get - { - lock (lockQueueCount) - { - return queueCount; - } - } - set - { - lock (lockQueueCount) - { - queueCount = value; - } - OnQueueCountChangedEvent(new QueueCountEventArgs(queueCount)); - } - } - - private QueueStateStruct queueState = - new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; - - public QueueStateStruct QueueState - { - get - { - lock (lockQueueState) - { - return queueState; - } - } - set - { - lock (lockQueueState) - { - queueState = value; - } - OnQueueStateChangedEvent(new QueueStateEventArgs(queueState)); - } - } - - public bool ProcessingCommands - { - get { return processingCommands; } - } - - public CommandProcessorHasher() - { - workerCommands.WorkerReportsProgress = true; - workerCommands.WorkerSupportsCancellation = true; - workerCommands.DoWork += new DoWorkEventHandler(WorkerCommands_DoWork); - workerCommands.RunWorkerCompleted += new RunWorkerCompletedEventHandler(WorkerCommands_RunWorkerCompleted); - } - - void WorkerCommands_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) - { - Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); - - processingCommands = false; - paused = false; - //logger.Trace("Stopping command worker (hasher)..."); - QueueState = new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; - QueueCount = 0; - } - - public void Init() - { - Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); - - processingCommands = true; - //logger.Trace("Starting command worker (hasher)..."); - QueueState = new QueueStateStruct() - { - queueState = QueueStateEnum.StartingHasher, - extraParams = new string[0] - }; - this.workerCommands.RunWorkerAsync(); - } - - public void Stop() - { - workerCommands.CancelAsync(); - } - - /// - /// This is simply used to tell the command processor that a new command has been added to the database - /// - public void NotifyOfNewCommand() - { - // if the worker is busy, it will pick up the next command from the DB - // do not pick new command if cancellation is requested - if (processingCommands || workerCommands.CancellationPending) - { - //logger.Trace("NotifyOfNewCommand (hasher) exiting, worker already busy"); - return; - } - - // otherwise need to start the worker again - //logger.Trace("Restarting command worker (hasher)..."); - - processingCommands = true; - if (!workerCommands.IsBusy) - this.workerCommands.RunWorkerAsync(); - } - - void WorkerCommands_DoWork(object sender, DoWorkEventArgs e) - { - while (true) - { - if (workerCommands.CancellationPending) - { - e.Cancel = true; - return; - } - - // if paused we will sleep for 5 seconds, and the try again - // we will remove the pause if it was set more than 6 hours ago - // the pause is initiated when banned from AniDB or manually by the user - if (Paused) - { - try - { - if (workerCommands.CancellationPending) - { - e.Cancel = true; - return; - } - - //logger.Trace("Hasher Queue is paused: {0}", pauseTime.Value); - TimeSpan ts = DateTime.Now - pauseTime.Value; - if (ts.TotalHours >= 6) - { - Paused = false; - } - } - catch - { - } - Thread.Sleep(200); - continue; - } - - //logger.Trace("Looking for next command request (hasher)..."); - - CommandRequest crdb = RepoFactory.CommandRequest.GetNextDBCommandRequestHasher(); - if (crdb == null) return; - - if (workerCommands.CancellationPending) - { - e.Cancel = true; - return; - } - - QueueCount = RepoFactory.CommandRequest.GetQueuedCommandCountHasher(); - //logger.Trace("{0} commands remaining in queue (hasher)", QueueCount); - - //logger.Trace("Next command request (hasher): {0}", crdb.CommandID); - - ICommandRequest icr = CommandHelper.GetCommand(crdb); - if (icr == null) - { - logger.Trace("No implementation found for command: {0}-{1}", crdb.CommandType, crdb.CommandID); - return; - } - - QueueState = icr.PrettyDescription; - - if (workerCommands.CancellationPending) - { - e.Cancel = true; - return; - } - - //logger.Trace("Processing command request (hasher): {0}", crdb.CommandID); - icr.ProcessCommand(); - - //logger.Trace("Deleting command request (hasher): {0}", crdb.CommandID); - RepoFactory.CommandRequest.Delete(crdb.CommandRequestID); - QueueCount = RepoFactory.CommandRequest.GetQueuedCommandCountHasher(); - } - } - } +using System; +using System.ComponentModel; +using System.Globalization; +using System.Threading; +using Shoko.Server.Repositories.Direct; +using NLog; +using Shoko.Commons.Queue; +using Shoko.Models.Queue; +using Shoko.Models.Server; +using Shoko.Server.Repositories; + +namespace Shoko.Server.Commands +{ + public class CommandProcessorHasher + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + private BackgroundWorker workerCommands = new BackgroundWorker(); + private bool processingCommands = false; + private DateTime? pauseTime = null; + + private object lockQueueCount = new object(); + private object lockQueueState = new object(); + private object lockPaused = new object(); + + public delegate void QueueCountChangedHandler(QueueCountEventArgs ev); + + public event QueueCountChangedHandler OnQueueCountChangedEvent; + + public delegate void QueueStateChangedHandler(QueueStateEventArgs ev); + + public event QueueStateChangedHandler OnQueueStateChangedEvent; + + private bool paused = false; + + public bool Paused + { + get + { + lock (lockPaused) + { + return paused; + } + } + set + { + lock (lockPaused) + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); + + paused = value; + if (paused) + { + QueueState = + new QueueStateStruct() {queueState = QueueStateEnum.Paused, extraParams = new string[0]}; + pauseTime = DateTime.Now; + } + else + { + QueueState = + new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; + pauseTime = null; + } + + ServerInfo.Instance.HasherQueuePaused = paused; + ServerInfo.Instance.HasherQueueRunning = !paused; + } + } + } + + private int queueCount = 0; + + public int QueueCount + { + get + { + lock (lockQueueCount) + { + return queueCount; + } + } + set + { + lock (lockQueueCount) + { + queueCount = value; + } + OnQueueCountChangedEvent?.Invoke(new QueueCountEventArgs(queueCount)); + } + } + + private QueueStateStruct queueState = + new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; + + public QueueStateStruct QueueState + { + get + { + lock (lockQueueState) + { + return queueState; + } + } + set + { + lock (lockQueueState) + { + queueState = value; + } + OnQueueCountChangedEvent?.Invoke(new QueueCountEventArgs(queueCount)); + } + } + + public bool ProcessingCommands + { + get { return processingCommands; } + } + + public CommandProcessorHasher() + { + workerCommands.WorkerReportsProgress = true; + workerCommands.WorkerSupportsCancellation = true; + workerCommands.DoWork += new DoWorkEventHandler(WorkerCommands_DoWork); + workerCommands.RunWorkerCompleted += new RunWorkerCompletedEventHandler(WorkerCommands_RunWorkerCompleted); + } + + void WorkerCommands_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); + + processingCommands = false; + paused = false; + //logger.Trace("Stopping command worker (hasher)..."); + QueueState = new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; + QueueCount = 0; + } + + public void Init() + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); + + processingCommands = true; + //logger.Trace("Starting command worker (hasher)..."); + QueueState = new QueueStateStruct() + { + queueState = QueueStateEnum.StartingHasher, + extraParams = new string[0] + }; + this.workerCommands.RunWorkerAsync(); + } + + public void Stop() + { + workerCommands.CancelAsync(); + } + + /// + /// This is simply used to tell the command processor that a new command has been added to the database + /// + public void NotifyOfNewCommand() + { + // if the worker is busy, it will pick up the next command from the DB + // do not pick new command if cancellation is requested + if (processingCommands || workerCommands.CancellationPending) + { + //logger.Trace("NotifyOfNewCommand (hasher) exiting, worker already busy"); + return; + } + + // otherwise need to start the worker again + //logger.Trace("Restarting command worker (hasher)..."); + + processingCommands = true; + if (!workerCommands.IsBusy) + this.workerCommands.RunWorkerAsync(); + } + + void WorkerCommands_DoWork(object sender, DoWorkEventArgs e) + { + while (true) + { + if (workerCommands.CancellationPending) + { + e.Cancel = true; + return; + } + + // if paused we will sleep for 5 seconds, and the try again + // we will remove the pause if it was set more than 6 hours ago + // the pause is initiated when banned from AniDB or manually by the user + if (Paused) + { + try + { + if (workerCommands.CancellationPending) + { + e.Cancel = true; + return; + } + + //logger.Trace("Hasher Queue is paused: {0}", pauseTime.Value); + TimeSpan ts = DateTime.Now - pauseTime.Value; + if (ts.TotalHours >= 6) + { + Paused = false; + } + } + catch + { + } + Thread.Sleep(200); + continue; + } + + //logger.Trace("Looking for next command request (hasher)..."); + + CommandRequest crdb = RepoFactory.CommandRequest.GetNextDBCommandRequestHasher(); + if (crdb == null) return; + + if (workerCommands.CancellationPending) + { + e.Cancel = true; + return; + } + + QueueCount = RepoFactory.CommandRequest.GetQueuedCommandCountHasher(); + //logger.Trace("{0} commands remaining in queue (hasher)", QueueCount); + + //logger.Trace("Next command request (hasher): {0}", crdb.CommandID); + + ICommandRequest icr = CommandHelper.GetCommand(crdb); + if (icr == null) + { + logger.Trace("No implementation found for command: {0}-{1}", crdb.CommandType, crdb.CommandID); + return; + } + + QueueState = icr.PrettyDescription; + + if (workerCommands.CancellationPending) + { + e.Cancel = true; + return; + } + + //logger.Trace("Processing command request (hasher): {0}", crdb.CommandID); + icr.ProcessCommand(); + + //logger.Trace("Deleting command request (hasher): {0}", crdb.CommandID); + RepoFactory.CommandRequest.Delete(crdb.CommandRequestID); + QueueCount = RepoFactory.CommandRequest.GetQueuedCommandCountHasher(); + } + } + } } \ No newline at end of file diff --git a/Shoko.Server/Commands/CommandProcessorImages.cs b/Shoko.Server/Commands/CommandProcessorImages.cs old mode 100644 new mode 100755 index 05405cceb..1a323511a --- a/Shoko.Server/Commands/CommandProcessorImages.cs +++ b/Shoko.Server/Commands/CommandProcessorImages.cs @@ -1,259 +1,259 @@ -using System; -using System.ComponentModel; -using System.Globalization; -using System.Threading; -using Shoko.Server.Repositories.Direct; -using NLog; -using Shoko.Commons.Queue; -using Shoko.Models.Queue; -using Shoko.Models.Server; -using Shoko.Server.Repositories; - -namespace Shoko.Server.Commands -{ - public class CommandProcessorImages : IDisposable - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - private BackgroundWorker workerCommands = new BackgroundWorker(); - private bool processingCommands = false; - private DateTime? pauseTime = null; - - private object lockQueueCount = new object(); - private object lockQueueState = new object(); - private object lockPaused = new object(); - - public delegate void QueueCountChangedHandler(QueueCountEventArgs ev); - - public event QueueCountChangedHandler OnQueueCountChangedEvent; - - public delegate void QueueStateChangedHandler(QueueStateEventArgs ev); - - public event QueueStateChangedHandler OnQueueStateChangedEvent; - - private bool paused = false; - - public bool Paused - { - get - { - lock (lockPaused) - { - return paused; - } - } - set - { - lock (lockPaused) - { - Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); - - paused = value; - if (paused) - { - QueueState = - new QueueStateStruct() {queueState = QueueStateEnum.Paused, extraParams = new string[0]}; - pauseTime = DateTime.Now; - } - else - { - QueueState = - new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; - pauseTime = null; - } - - ServerInfo.Instance.ImagesQueuePaused = paused; - ServerInfo.Instance.ImagesQueueRunning = !paused; - } - } - } - - private int queueCount = 0; - - public int QueueCount - { - get - { - lock (lockQueueCount) - { - return queueCount; - } - } - set - { - lock (lockQueueCount) - { - queueCount = value; - } - OnQueueCountChangedEvent(new QueueCountEventArgs(queueCount)); - } - } - - private QueueStateStruct queueState = - new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; - - public QueueStateStruct QueueState - { - get - { - lock (lockQueueState) - { - return queueState; - } - } - set - { - lock (lockQueueState) - { - queueState = value; - } - OnQueueStateChangedEvent(new QueueStateEventArgs(queueState)); - } - } - - public bool ProcessingCommands - { - get { return processingCommands; } - } - - public CommandProcessorImages() - { - workerCommands.WorkerReportsProgress = true; - workerCommands.WorkerSupportsCancellation = true; - workerCommands.DoWork += new DoWorkEventHandler(WorkerCommands_DoWork); - workerCommands.RunWorkerCompleted += new RunWorkerCompletedEventHandler(WorkerCommands_RunWorkerCompleted); - } - - void WorkerCommands_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) - { - Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); - - processingCommands = false; - paused = false; - //logger.Trace("Stopping command worker (images)..."); - QueueState = new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; - QueueCount = 0; - } - - public void Init() - { - Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); - - processingCommands = true; - //logger.Trace("Starting command worker (images)..."); - QueueState = new QueueStateStruct() - { - queueState = QueueStateEnum.StartingImages, - extraParams = new string[0] - }; - this.workerCommands.RunWorkerAsync(); - } - - public void Stop() - { - workerCommands.CancelAsync(); - } - - /// - /// This is simply used to tell the command processor that a new command has been added to the database - /// - public void NotifyOfNewCommand() - { - // if the worker is busy, it will pick up the next command from the DB - // do not pick new command if cancellation is requested - if (processingCommands || workerCommands.CancellationPending) - { - //logger.Trace("NotifyOfNewCommand (images) exiting, worker already busy"); - return; - } - - // otherwise need to start the worker again - //logger.Trace("Restarting command worker (images)..."); - - processingCommands = true; - if (!workerCommands.IsBusy) - this.workerCommands.RunWorkerAsync(); - } - - void WorkerCommands_DoWork(object sender, DoWorkEventArgs e) - { - while (true) - { - if (workerCommands.CancellationPending) - { - e.Cancel = true; - return; - } - - // if paused we will sleep for 5 seconds, and the try again - // we will remove the pause if it was set more than 6 hours ago - // the pause is initiated when banned from AniDB or manually by the user - if (Paused) - { - try - { - if (workerCommands.CancellationPending) - { - e.Cancel = true; - return; - } - - //logger.Trace("Images Queue is paused: {0}", pauseTime.Value); - TimeSpan ts = DateTime.Now - pauseTime.Value; - if (ts.TotalHours >= 6) - { - Paused = false; - } - } - catch - { - } - Thread.Sleep(200); - continue; - } - - //logger.Trace("Looking for next command request (images)..."); - - CommandRequest crdb = RepoFactory.CommandRequest.GetNextDBCommandRequestImages(); - if (crdb == null) return; - - QueueCount = RepoFactory.CommandRequest.GetQueuedCommandCountImages(); - //logger.Trace("{0} commands remaining in queue (images)", QueueCount); - - if (workerCommands.CancellationPending) - { - e.Cancel = true; - return; - } - - //logger.Trace("Next command request (images): {0}", crdb.CommandID); - - ICommandRequest icr = CommandHelper.GetCommand(crdb); - if (icr == null) - { - //logger.Trace("No implementation found for command: {0}-{1}", crdb.CommandType, crdb.CommandID); - return; - } - - if (workerCommands.CancellationPending) - { - e.Cancel = true; - return; - } - - QueueState = icr.PrettyDescription; - - //logger.Trace("Processing command request (images): {0}", crdb.CommandID); - icr.ProcessCommand(); - - //logger.Trace("Deleting command request (images): {0}", crdb.CommandID); - RepoFactory.CommandRequest.Delete(crdb.CommandRequestID); - QueueCount = RepoFactory.CommandRequest.GetQueuedCommandCountImages(); - } - } - - public void Dispose() - { - this.workerCommands.Dispose(); - } - } +using System; +using System.ComponentModel; +using System.Globalization; +using System.Threading; +using Shoko.Server.Repositories.Direct; +using NLog; +using Shoko.Commons.Queue; +using Shoko.Models.Queue; +using Shoko.Models.Server; +using Shoko.Server.Repositories; + +namespace Shoko.Server.Commands +{ + public class CommandProcessorImages : IDisposable + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + private BackgroundWorker workerCommands = new BackgroundWorker(); + private bool processingCommands = false; + private DateTime? pauseTime = null; + + private object lockQueueCount = new object(); + private object lockQueueState = new object(); + private object lockPaused = new object(); + + public delegate void QueueCountChangedHandler(QueueCountEventArgs ev); + + public event QueueCountChangedHandler OnQueueCountChangedEvent; + + public delegate void QueueStateChangedHandler(QueueStateEventArgs ev); + + public event QueueStateChangedHandler OnQueueStateChangedEvent; + + private bool paused = false; + + public bool Paused + { + get + { + lock (lockPaused) + { + return paused; + } + } + set + { + lock (lockPaused) + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); + + paused = value; + if (paused) + { + QueueState = + new QueueStateStruct() {queueState = QueueStateEnum.Paused, extraParams = new string[0]}; + pauseTime = DateTime.Now; + } + else + { + QueueState = + new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; + pauseTime = null; + } + + ServerInfo.Instance.ImagesQueuePaused = paused; + ServerInfo.Instance.ImagesQueueRunning = !paused; + } + } + } + + private int queueCount = 0; + + public int QueueCount + { + get + { + lock (lockQueueCount) + { + return queueCount; + } + } + set + { + lock (lockQueueCount) + { + queueCount = value; + } + OnQueueCountChangedEvent?.Invoke(new QueueCountEventArgs(queueCount)); + } + } + + private QueueStateStruct queueState = + new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; + + public QueueStateStruct QueueState + { + get + { + lock (lockQueueState) + { + return queueState; + } + } + set + { + lock (lockQueueState) + { + queueState = value; + } + OnQueueCountChangedEvent?.Invoke(new QueueCountEventArgs(queueCount)); + } + } + + public bool ProcessingCommands + { + get { return processingCommands; } + } + + public CommandProcessorImages() + { + workerCommands.WorkerReportsProgress = true; + workerCommands.WorkerSupportsCancellation = true; + workerCommands.DoWork += new DoWorkEventHandler(WorkerCommands_DoWork); + workerCommands.RunWorkerCompleted += new RunWorkerCompletedEventHandler(WorkerCommands_RunWorkerCompleted); + } + + void WorkerCommands_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); + + processingCommands = false; + paused = false; + //logger.Trace("Stopping command worker (images)..."); + QueueState = new QueueStateStruct() {queueState = QueueStateEnum.Idle, extraParams = new string[0]}; + QueueCount = 0; + } + + public void Init() + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); + + processingCommands = true; + //logger.Trace("Starting command worker (images)..."); + QueueState = new QueueStateStruct() + { + queueState = QueueStateEnum.StartingImages, + extraParams = new string[0] + }; + this.workerCommands.RunWorkerAsync(); + } + + public void Stop() + { + workerCommands.CancelAsync(); + } + + /// + /// This is simply used to tell the command processor that a new command has been added to the database + /// + public void NotifyOfNewCommand() + { + // if the worker is busy, it will pick up the next command from the DB + // do not pick new command if cancellation is requested + if (processingCommands || workerCommands.CancellationPending) + { + //logger.Trace("NotifyOfNewCommand (images) exiting, worker already busy"); + return; + } + + // otherwise need to start the worker again + //logger.Trace("Restarting command worker (images)..."); + + processingCommands = true; + if (!workerCommands.IsBusy) + this.workerCommands.RunWorkerAsync(); + } + + void WorkerCommands_DoWork(object sender, DoWorkEventArgs e) + { + while (true) + { + if (workerCommands.CancellationPending) + { + e.Cancel = true; + return; + } + + // if paused we will sleep for 5 seconds, and the try again + // we will remove the pause if it was set more than 6 hours ago + // the pause is initiated when banned from AniDB or manually by the user + if (Paused) + { + try + { + if (workerCommands.CancellationPending) + { + e.Cancel = true; + return; + } + + //logger.Trace("Images Queue is paused: {0}", pauseTime.Value); + TimeSpan ts = DateTime.Now - pauseTime.Value; + if (ts.TotalHours >= 6) + { + Paused = false; + } + } + catch + { + } + Thread.Sleep(200); + continue; + } + + //logger.Trace("Looking for next command request (images)..."); + + CommandRequest crdb = RepoFactory.CommandRequest.GetNextDBCommandRequestImages(); + if (crdb == null) return; + + QueueCount = RepoFactory.CommandRequest.GetQueuedCommandCountImages(); + //logger.Trace("{0} commands remaining in queue (images)", QueueCount); + + if (workerCommands.CancellationPending) + { + e.Cancel = true; + return; + } + + //logger.Trace("Next command request (images): {0}", crdb.CommandID); + + ICommandRequest icr = CommandHelper.GetCommand(crdb); + if (icr == null) + { + //logger.Trace("No implementation found for command: {0}-{1}", crdb.CommandType, crdb.CommandID); + return; + } + + if (workerCommands.CancellationPending) + { + e.Cancel = true; + return; + } + + QueueState = icr.PrettyDescription; + + //logger.Trace("Processing command request (images): {0}", crdb.CommandID); + icr.ProcessCommand(); + + //logger.Trace("Deleting command request (images): {0}", crdb.CommandID); + RepoFactory.CommandRequest.Delete(crdb.CommandRequestID); + QueueCount = RepoFactory.CommandRequest.GetQueuedCommandCountImages(); + } + } + + public void Dispose() + { + this.workerCommands.Dispose(); + } + } } \ No newline at end of file diff --git a/Shoko.Server/Commands/CommandRequestImplementation.cs b/Shoko.Server/Commands/CommandRequestImplementation.cs index be9cc4843..b9a89ad8e 100644 --- a/Shoko.Server/Commands/CommandRequestImplementation.cs +++ b/Shoko.Server/Commands/CommandRequestImplementation.cs @@ -13,7 +13,7 @@ namespace Shoko.Server.Commands { public abstract class CommandRequestImplementation { - protected static Logger logger = LogManager.GetCurrentClassLogger(); + protected static readonly Logger logger = LogManager.GetCurrentClassLogger(); // ignoring the base properties so that when we serialize we only get the properties // defined in the concrete class @@ -80,30 +80,35 @@ public void Save(ISession session) // we will always mylist watched state changes // this is because the user may be toggling the status in the client, and we need to process // them all in the order they were requested - if (CommandType != (int) CommandRequestType.AniDB_UpdateWatchedUDP) - { - //logger.Trace("Command already in queue with identifier so skipping: {0}", this.CommandID); + if (CommandType == (int) CommandRequestType.AniDB_UpdateWatchedUDP) + RepoFactory.CommandRequest.Delete(crTemp); + else return; - } } - CommandRequest cri = this.ToDatabaseObject(); + CommandRequest cri = ToDatabaseObject(); RepoFactory.CommandRequest.Save(cri); - if (CommandType == (int) CommandRequestType.HashFile) - ShokoService.CmdProcessorHasher.NotifyOfNewCommand(); - else if (CommandType == (int) CommandRequestType.ImageDownload || - CommandType == (int) CommandRequestType.ValidateAllImages) - ShokoService.CmdProcessorImages.NotifyOfNewCommand(); - else - ShokoService.CmdProcessorGeneral.NotifyOfNewCommand(); + switch (CommandType) + { + case (int) CommandRequestType.HashFile: + ShokoService.CmdProcessorHasher.NotifyOfNewCommand(); + break; + case (int) CommandRequestType.ImageDownload: + case (int) CommandRequestType.ValidateAllImages: + ShokoService.CmdProcessorImages.NotifyOfNewCommand(); + break; + default: + ShokoService.CmdProcessorGeneral.NotifyOfNewCommand(); + break; + } } protected string TryGetProperty(XmlDocument doc, string keyName, string propertyName) { try { - string prop = doc[keyName][propertyName].InnerText.Trim(); + string prop = doc?[keyName]?[propertyName]?.InnerText.Trim() ?? string.Empty; return prop; } catch diff --git a/Shoko.Server/Commands/Import/CommandRequest_DownloadImage.cs b/Shoko.Server/Commands/Import/CommandRequest_DownloadImage.cs index f896c1f9c..a0391a3db 100644 --- a/Shoko.Server/Commands/Import/CommandRequest_DownloadImage.cs +++ b/Shoko.Server/Commands/Import/CommandRequest_DownloadImage.cs @@ -11,9 +11,10 @@ using Shoko.Models.Enums; using Shoko.Models.Queue; using Shoko.Models.Server; -using Shoko.Server.Models; +using Shoko.Server.AniDB_API; using Shoko.Server.Extensions; using Shoko.Server.ImageDownload; +using Shoko.Server.Models; using Shoko.Server.Repositories; using Directory = Pri.LongPath.Directory; using File = Pri.LongPath.File; @@ -69,7 +70,7 @@ public QueueStateStruct PrettyDescription type = string.Empty; break; } - return new QueueStateStruct() + return new QueueStateStruct { queueState = QueueStateEnum.DownloadImage, extraParams = new[] { type, EntityID.ToString() } @@ -83,11 +84,11 @@ public CommandRequest_DownloadImage() public CommandRequest_DownloadImage(int entityID, ImageEntityType entityType, bool forced) { - this.EntityID = entityID; - this.EntityType = (int) entityType; - this.ForceDownload = forced; - this.CommandType = (int) CommandRequestType.ImageDownload; - this.Priority = (int) DefaultPriority; + EntityID = entityID; + EntityType = (int) entityType; + ForceDownload = forced; + CommandType = (int) CommandRequestType.ImageDownload; + Priority = (int) DefaultPriority; GenerateCommandID(); } @@ -96,6 +97,7 @@ public override void ProcessCommand() { logger.Info("Processing CommandRequest_DownloadImage: {0}", EntityID); string downloadURL = string.Empty; + try { ImageDownloadRequest req = null; @@ -172,6 +174,7 @@ public override void ProcessCommand() logger.Warn($"AniDB poster image failed to download: Can't find AniDB_Anime with ID: {EntityID}"); return; } + AniDbImageRateLimiter.Instance.EnsureRate(); req = new ImageDownloadRequest(EntityTypeEnum, anime, ForceDownload); break; @@ -182,6 +185,7 @@ public override void ProcessCommand() logger.Warn($"AniDB Character image failed to download: Can't find AniDB Character with ID: {EntityID}"); return; } + AniDbImageRateLimiter.Instance.EnsureRate(); req = new ImageDownloadRequest(EntityTypeEnum, chr, ForceDownload); break; @@ -192,6 +196,7 @@ public override void ProcessCommand() logger.Warn($"AniDB Seiyuu image failed to download: Can't find Seiyuu with ID: {EntityID}"); return; } + AniDbImageRateLimiter.Instance.EnsureRate(); req = new ImageDownloadRequest(EntityTypeEnum, creator, ForceDownload); break; } @@ -229,8 +234,9 @@ public override void ProcessCommand() bool downloadImage = true; bool fileExists = File.Exists(fileName); + bool imageValid = fileExists && Misc.IsImageValid(fileName); - if (fileExists && !req.ForceDownload) downloadImage = false; + if (imageValid && !req.ForceDownload) downloadImage = false; if (!downloadImage) continue; @@ -238,15 +244,13 @@ public override void ProcessCommand() try { - if (fileExists) File.Delete(fileName); + if (fileExists && !imageValid) File.Delete(fileName); } catch (Exception ex) { Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); - string msg = string.Format(Commons.Properties.Resources.Command_DeleteError, fileName, - ex.Message); - logger.Warn(msg); + logger.Warn(Resources.Command_DeleteError, fileName, ex.Message); return; } @@ -472,28 +476,28 @@ private string GetFileName(ImageDownloadRequest req, bool thumbNailOnly) public override void GenerateCommandID() { - this.CommandID = $"CommandRequest_DownloadImage_{EntityID}_{EntityType}"; + CommandID = $"CommandRequest_DownloadImage_{EntityID}_{EntityType}"; } public override bool LoadFromDBCommand(CommandRequest cq) { - this.CommandID = cq.CommandID; - this.CommandRequestID = cq.CommandRequestID; - this.CommandType = cq.CommandType; - this.Priority = cq.Priority; - this.CommandDetails = cq.CommandDetails; - this.DateTimeUpdated = cq.DateTimeUpdated; + CommandID = cq.CommandID; + CommandRequestID = cq.CommandRequestID; + CommandType = cq.CommandType; + Priority = cq.Priority; + CommandDetails = cq.CommandDetails; + DateTimeUpdated = cq.DateTimeUpdated; // read xml to get parameters - if (this.CommandDetails.Trim().Length > 0) + if (CommandDetails.Trim().Length > 0) { XmlDocument docCreator = new XmlDocument(); - docCreator.LoadXml(this.CommandDetails); + docCreator.LoadXml(CommandDetails); // populate the fields - this.EntityID = int.Parse(TryGetProperty(docCreator, "CommandRequest_DownloadImage", "EntityID")); - this.EntityType = int.Parse(TryGetProperty(docCreator, "CommandRequest_DownloadImage", "EntityType")); - this.ForceDownload = + EntityID = int.Parse(TryGetProperty(docCreator, "CommandRequest_DownloadImage", "EntityID")); + EntityType = int.Parse(TryGetProperty(docCreator, "CommandRequest_DownloadImage", "EntityType")); + ForceDownload = bool.Parse(TryGetProperty(docCreator, "CommandRequest_DownloadImage", "ForceDownload")); } @@ -506,10 +510,10 @@ public override CommandRequest ToDatabaseObject() CommandRequest cq = new CommandRequest { - CommandID = this.CommandID, - CommandType = this.CommandType, - Priority = this.Priority, - CommandDetails = this.ToXML(), + CommandID = CommandID, + CommandType = CommandType, + Priority = Priority, + CommandDetails = ToXML(), DateTimeUpdated = DateTime.Now }; return cq; diff --git a/Shoko.Server/Commands/Import/CommandRequest_HashFile.cs b/Shoko.Server/Commands/Import/CommandRequest_HashFile.cs index f78d10753..17a3dc5c4 100644 --- a/Shoko.Server/Commands/Import/CommandRequest_HashFile.cs +++ b/Shoko.Server/Commands/Import/CommandRequest_HashFile.cs @@ -222,7 +222,7 @@ private SVR_VideoLocal_Place ProcessFile_LocalInfo() ImportFolderID = nshareID, ImportFolderType = folder.ImportFolderType }; - // Male sure we have an ID + // Make sure we have an ID RepoFactory.VideoLocalPlace.Save(vlocalplace); } @@ -289,7 +289,7 @@ private SVR_VideoLocal_Place ProcessFile_LocalInfo() DateTime start = DateTime.Now; logger.Trace("Calculating ED2K hashes for: {0}", FileName); // update the VideoLocal record with the Hash, since cloud support we calculate everything - var hashes = FileHashHelper.GetHashInfo(FileName.Replace("/", "\\"), true, ShokoServer.OnHashProgress, + var hashes = FileHashHelper.GetHashInfo(FileName.Replace("/", $"{System.IO.Path.DirectorySeparatorChar}"), true, ShokoServer.OnHashProgress, true, true, true); TimeSpan ts = DateTime.Now - start; logger.Trace("Hashed file in {0:#0.0} seconds --- {1} ({2})", ts.TotalSeconds, FileName, @@ -310,9 +310,13 @@ private SVR_VideoLocal_Place ProcessFile_LocalInfo() if (tlocal != null) { - List preps = tlocal.Places.Where( + // Aid with hashing cloud. Merge hashes and save, regardless of duplicate file + changed = tlocal.MergeInfoFrom(vlocal); + vlocal = tlocal; + + List preps = vlocal.Places.Where( a => a.ImportFolder.CloudID == folder.CloudID && - vlocalplace.VideoLocal_Place_ID != a.VideoLocal_Place_ID).ToList(); + !vlocalplace.FullServerPath.Equals(a.FullServerPath)).ToList(); foreach (var prep in preps) { if (prep == null) continue; @@ -330,13 +334,9 @@ private SVR_VideoLocal_Place ProcessFile_LocalInfo() } } - // Aid with hashing cloud. Merge hashes and save, regardless of duplicate file - changed = tlocal.MergeInfoFrom(vlocal); - vlocal = tlocal; - - var dupPlace = tlocal.Places.FirstOrDefault( + var dupPlace = vlocal.Places.FirstOrDefault( a => a.ImportFolder.CloudID == folder.CloudID && - vlocalplace.VideoLocal_Place_ID != a.VideoLocal_Place_ID); + !vlocalplace.FullServerPath.Equals(a.FullServerPath)); if (dupPlace != null) { @@ -455,7 +455,7 @@ private void FillMissingHashes(SVR_VideoLocal vlocal) tp.Add("CRC32"); logger.Trace("Calculating missing {1} hashes for: {0}", FileName, string.Join(",", tp)); // update the VideoLocal record with the Hash, since cloud support we calculate everything - Hashes hashes = FileHashHelper.GetHashInfo(FileName.Replace("/", "\\"), true, ShokoServer.OnHashProgress, + Hashes hashes = FileHashHelper.GetHashInfo(FileName.Replace("/", $"{System.IO.Path.DirectorySeparatorChar}"), true, ShokoServer.OnHashProgress, needcrc32, needmd5, needsha1); TimeSpan ts = DateTime.Now - start; logger.Trace("Hashed file in {0} seconds --- {1} ({2})", ts.TotalSeconds.ToString("#0.0"), @@ -680,4 +680,4 @@ public override CommandRequest ToDatabaseObject() return cq; } } -} \ No newline at end of file +} diff --git a/Shoko.Server/Commands/Import/CommandRequest_ProcessFile.cs b/Shoko.Server/Commands/Import/CommandRequest_ProcessFile.cs index c595e458d..c7c3ad1da 100644 --- a/Shoko.Server/Commands/Import/CommandRequest_ProcessFile.cs +++ b/Shoko.Server/Commands/Import/CommandRequest_ProcessFile.cs @@ -5,12 +5,12 @@ using System.Threading; using System.Xml; using AniDBAPI; -using Shoko.Models.Server; -using Shoko.Server.Commands.AniDB; -using NutzCode.CloudFileSystem; using Pri.LongPath; using Shoko.Commons.Queue; +using Shoko.Models.Azure; using Shoko.Models.Queue; +using Shoko.Models.Server; +using Shoko.Server.Commands.AniDB; using Shoko.Server.Models; using Shoko.Server.Providers.Azure; using Shoko.Server.Repositories; @@ -23,29 +23,25 @@ public class CommandRequest_ProcessFile : CommandRequestImplementation, ICommand public int VideoLocalID { get; set; } public bool ForceAniDB { get; set; } - private SVR_VideoLocal vlocal = null; + private SVR_VideoLocal vlocal; - public CommandRequestPriority DefaultPriority - { - get { return CommandRequestPriority.Priority3; } - } + public CommandRequestPriority DefaultPriority => CommandRequestPriority.Priority3; public QueueStateStruct PrettyDescription { get { if (vlocal != null) - return new QueueStateStruct() + return new QueueStateStruct { queueState = QueueStateEnum.FileInfo, extraParams = new[] {vlocal.FileName} }; - else - return new QueueStateStruct() - { - queueState = QueueStateEnum.FileInfo, - extraParams = new[] {VideoLocalID.ToString()} - }; + return new QueueStateStruct + { + queueState = QueueStateEnum.FileInfo, + extraParams = new[] {VideoLocalID.ToString()} + }; } } @@ -55,17 +51,17 @@ public CommandRequest_ProcessFile() public CommandRequest_ProcessFile(int vidLocalID, bool forceAniDB) { - this.VideoLocalID = vidLocalID; - this.ForceAniDB = forceAniDB; - this.CommandType = (int) CommandRequestType.ProcessFile; - this.Priority = (int) DefaultPriority; + VideoLocalID = vidLocalID; + ForceAniDB = forceAniDB; + CommandType = (int) CommandRequestType.ProcessFile; + Priority = (int) DefaultPriority; GenerateCommandID(); } public override void ProcessCommand() { - logger.Trace("Processing File: {0}", VideoLocalID); + logger.Trace($"Processing File: {VideoLocalID}"); Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); @@ -79,18 +75,13 @@ public override void ProcessCommand() } catch (Exception ex) { - logger.Error("Error processing CommandRequest_ProcessFile: {0} - {1}", VideoLocalID, ex.ToString()); - return; + logger.Error($"Error processing CommandRequest_ProcessFile: {VideoLocalID} - {ex}"); } - - // TODO update stats for group and series - - // TODO check for TvDB } private void ProcessFile_AniDB(SVR_VideoLocal vidLocal) { - logger.Trace("Checking for AniDB_File record for: {0} --- {1}", vidLocal.Hash, vidLocal.FileName); + logger.Trace($"Checking for AniDB_File record for: {vidLocal.Hash} --- {vidLocal.FileName}"); // check if we already have this AniDB_File info in the database lock (vidLocal) @@ -173,20 +164,19 @@ private void ProcessFile_AniDB(SVR_VideoLocal vidLocal) // lets see if we can find the episode/anime info from the web cache if (ServerSettings.WebCache_XRefFileEpisode_Get) { - List xrefs = + List xrefs = AzureWebAPI.Get_CrossRefFileEpisode(vidLocal); crossRefs = new List(); if (xrefs == null || xrefs.Count == 0) { logger.Debug( - "Cannot find AniDB_File record or get cross ref from web cache record so exiting: {0}", - vidLocal.ED2KHash); + $"Cannot find AniDB_File record or get cross ref from web cache record so exiting: {vidLocal.ED2KHash}"); return; } string fileName = vidLocal.GetBestVideoLocalPlace().FullServerPath; fileName = !string.IsNullOrEmpty(fileName) ? Path.GetFileName(fileName) : vidLocal.FileName; - foreach (Shoko.Models.Azure.Azure_CrossRef_File_Episode xref in xrefs) + foreach (Azure_CrossRef_File_Episode xref in xrefs) { CrossRef_File_Episode xrefEnt = new CrossRef_File_Episode { @@ -219,7 +209,7 @@ private void ProcessFile_AniDB(SVR_VideoLocal vidLocal) } else { - logger.Debug("Cannot get AniDB_File record so exiting: {0}", vidLocal.ED2KHash); + logger.Debug($"Cannot get AniDB_File record so exiting: {vidLocal.ED2KHash}"); return; } } @@ -243,7 +233,7 @@ private void ProcessFile_AniDB(SVR_VideoLocal vidLocal) animeID = aniFile.AnimeID; // if we have the anidb file, but no cross refs it means something has been broken - logger.Debug("Could not find any cross ref records for: {0}", vidLocal.ED2KHash); + logger.Debug($"Could not find any cross ref records for: {vidLocal.ED2KHash}"); missingEpisodes = true; } else @@ -333,16 +323,11 @@ private void ProcessFile_AniDB(SVR_VideoLocal vidLocal) } } } - vidLocal.Places.ForEach(a => { a.RenameAndMoveAsRequired(); }); - - // It imports default unwatched, so we only need it to do anything if we want it watched - if (aniFile != null && ServerSettings.AniDB_MyList_ReadWatched) + else { - CommandRequest_GetFileMyListStatus mylistcmd = - new CommandRequest_GetFileMyListStatus(aniFile.AniDB_FileID, vidLocal.FileName); - mylistcmd.Save(); + logger.Warn($"Unable to create AniDB_Anime for file: {vidLocal.FileName}"); } - + vidLocal.Places.ForEach(a => { a.RenameAndMoveAsRequired(); }); // update stats for groups and series // update all the groups above this series in the heirarchy @@ -364,27 +349,27 @@ private void ProcessFile_AniDB(SVR_VideoLocal vidLocal) /// public override void GenerateCommandID() { - this.CommandID = $"CommandRequest_ProcessFile_{VideoLocalID}"; + CommandID = $"CommandRequest_ProcessFile_{VideoLocalID}"; } public override bool LoadFromDBCommand(CommandRequest cq) { - this.CommandID = cq.CommandID; - this.CommandRequestID = cq.CommandRequestID; - this.CommandType = cq.CommandType; - this.Priority = cq.Priority; - this.CommandDetails = cq.CommandDetails; - this.DateTimeUpdated = cq.DateTimeUpdated; + CommandID = cq.CommandID; + CommandRequestID = cq.CommandRequestID; + CommandType = cq.CommandType; + Priority = cq.Priority; + CommandDetails = cq.CommandDetails; + DateTimeUpdated = cq.DateTimeUpdated; // read xml to get parameters - if (this.CommandDetails.Trim().Length > 0) + if (CommandDetails.Trim().Length > 0) { XmlDocument docCreator = new XmlDocument(); - docCreator.LoadXml(this.CommandDetails); + docCreator.LoadXml(CommandDetails); // populate the fields - this.VideoLocalID = int.Parse(TryGetProperty(docCreator, "CommandRequest_ProcessFile", "VideoLocalID")); - this.ForceAniDB = bool.Parse(TryGetProperty(docCreator, "CommandRequest_ProcessFile", "ForceAniDB")); + VideoLocalID = int.Parse(TryGetProperty(docCreator, "CommandRequest_ProcessFile", "VideoLocalID")); + ForceAniDB = bool.Parse(TryGetProperty(docCreator, "CommandRequest_ProcessFile", "ForceAniDB")); vlocal = RepoFactory.VideoLocal.GetByID(VideoLocalID); } @@ -397,10 +382,10 @@ public override CommandRequest ToDatabaseObject() CommandRequest cq = new CommandRequest { - CommandID = this.CommandID, - CommandType = this.CommandType, - Priority = this.Priority, - CommandDetails = this.ToXML(), + CommandID = CommandID, + CommandType = CommandType, + Priority = Priority, + CommandDetails = ToXML(), DateTimeUpdated = DateTime.Now }; return cq; diff --git a/Shoko.Server/Databases/BaseDatabase.cs b/Shoko.Server/Databases/BaseDatabase.cs index c30a2d755..8c70bfd17 100644 --- a/Shoko.Server/Databases/BaseDatabase.cs +++ b/Shoko.Server/Databases/BaseDatabase.cs @@ -45,6 +45,12 @@ public string GetDatabaseBackupName(int version) protected abstract ArrayList ExecuteReader(T connection, string command); public abstract string GetConnectionString(); + public virtual bool TestConnection() + { + // For SQLite, we assume conection succeeds + return true; + } + protected abstract void ConnectionWrapper(string connectionstring, Action action); protected Dictionary> AllVersions { get; set; } @@ -111,19 +117,16 @@ public ArrayList GetData(string sql) internal void PreFillVersions(IEnumerable commands) { - if (AllVersions.Count == 1 && AllVersions.Values.ElementAt(0).Count == 1) + if (AllVersions.Count != 1 || AllVersions.Values.ElementAt(0).Count != 1) return; + + Versions v = AllVersions.Values.ElementAt(0).Values.ElementAt(0); + string value = v.VersionValue; + AllVersions.Clear(); + RepoFactory.Versions.Delete(v); + foreach (DatabaseCommand dc in commands) { - Versions v = AllVersions.Values.ElementAt(0).Values.ElementAt(0); - string value = v.VersionValue; - AllVersions.Clear(); - RepoFactory.Versions.Delete(v); - foreach (DatabaseCommand dc in commands) - { - if (dc.Version <= int.Parse(value)) - { - AddVersion(dc.Version.ToString(), dc.Revision.ToString(), dc.CommandName); - } - } + if (dc.Version <= int.Parse(value)) + AddVersion(dc.Version.ToString(), dc.Revision.ToString(), dc.CommandName); } } @@ -151,12 +154,10 @@ public void AddFix(DatabaseCommand cmd) public Tuple ExecuteCommand(T connection, DatabaseCommand cmd) { - if (cmd.Version != 0 && cmd.Revision != 0) - { - if (AllVersions.ContainsKey(cmd.Version.ToString()) && - AllVersions[cmd.Version.ToString()].ContainsKey(cmd.Revision.ToString())) - return new Tuple(true, null); - } + if (cmd.Version != 0 && cmd.Revision != 0 && AllVersions.ContainsKey(cmd.Version.ToString()) && + AllVersions[cmd.Version.ToString()].ContainsKey(cmd.Revision.ToString())) + return new Tuple(true, null); + Tuple ret; switch (cmd.Type) @@ -180,9 +181,8 @@ public Tuple ExecuteCommand(T connection, DatabaseCommand cmd) break; } if (cmd.Version != 0 && ret.Item1 && cmd.Type != DatabaseCommandType.PostDatabaseFix) - { AddVersion(cmd.Version.ToString(), cmd.Revision.ToString(), cmd.CommandName); - } + return ret; } @@ -216,7 +216,7 @@ private void CreateInitialGroupFilters() { // group filters - if (RepoFactory.GroupFilter.GetAll().Count() > 0) return; + if (RepoFactory.GroupFilter.GetAll().Any()) return; Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); @@ -371,7 +371,7 @@ private void CreateInitialGroupFilters() private void CreateInitialUsers() { - if (RepoFactory.JMMUser.GetAll().Count() > 0) return; + if (RepoFactory.JMMUser.GetAll().Any()) return; Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ServerSettings.Culture); @@ -382,8 +382,8 @@ private void CreateInitialUsers() IsAdmin = 1, IsAniDBUser = 1, IsTraktUser = 1, - Password = string.Empty, - Username = Commons.Properties.Resources.Users_Default + Password = ServerSettings.DefaultUserPassword, + Username = ServerSettings.DefaultUserUsername }; RepoFactory.JMMUser.Save(defaultUser, true); @@ -402,7 +402,7 @@ private void CreateInitialUsers() private void CreateInitialRenameScript() { - if (RepoFactory.RenameScript.GetAll().Count() > 0) return; + if (RepoFactory.RenameScript.GetAll().Any()) return; RenameScript initialScript = new RenameScript(); diff --git a/Shoko.Server/Databases/DatabaseFactory.cs b/Shoko.Server/Databases/DatabaseFactory.cs old mode 100644 new mode 100755 index 4e0ac5a18..49ab0737b --- a/Shoko.Server/Databases/DatabaseFactory.cs +++ b/Shoko.Server/Databases/DatabaseFactory.cs @@ -65,7 +65,7 @@ public static IDatabase Instance set { _instance = value; } } - public static bool InitDB() + public static bool InitDB(out string errorMessage) { try { @@ -86,6 +86,7 @@ public static bool InitDB() { ServerState.Instance.CurrentSetupStatus = Commons.Properties.Resources.Database_NotSupportedVersion; + errorMessage = Commons.Properties.Resources.Database_NotSupportedVersion; return false; } if (version != 0 && version < Instance.RequiredVersion) @@ -95,7 +96,10 @@ public static bool InitDB() } try { + logger.Info($"{Instance.GetType()}Instance.CreateAndUpdateSchema()"); Instance.CreateAndUpdateSchema(); + + logger.Info($"RepoFactory.Init()"); RepoFactory.Init(); Instance.ExecuteDatabaseFixes(); Instance.PopulateInitialData(); @@ -106,28 +110,33 @@ public static bool InitDB() if (ex is DatabaseCommandException) { logger.Error(ex, ex.ToString()); - Utils.ShowErrorMessage("Database Error :\n\r " + ex.ToString() + + Utils.ShowErrorMessage("Database Error :\n\r " + ex + "\n\rNotify developers about this error, it will be logged in your logs", "Database Error"); ServerState.Instance.CurrentSetupStatus = Commons.Properties.Resources.Server_DatabaseFail; + errorMessage = "Database Error :\n\r " + ex + + "\n\rNotify developers about this error, it will be logged in your logs"; return false; } if (ex is TimeoutException) { - logger.Error(ex, "Database TimeOut: " + ex.ToString()); + logger.Error(ex, $"Database Timeout: {ex}"); ServerState.Instance.CurrentSetupStatus = Commons.Properties.Resources.Server_DatabaseTimeOut; + errorMessage = Commons.Properties.Resources.Server_DatabaseTimeOut + "\n\r" + ex; return false; } // throw to the outer try/catch throw; } + errorMessage = string.Empty; return true; } catch (Exception ex) { - logger.Error(ex, "Could not init database: " + ex.ToString()); + errorMessage = $"Could not init database: {ex}"; + logger.Error(ex, errorMessage); ServerState.Instance.CurrentSetupStatus = Commons.Properties.Resources.Server_DatabaseFail; return false; } diff --git a/Shoko.Server/Databases/DatabaseFixes.cs b/Shoko.Server/Databases/DatabaseFixes.cs index a24135c97..64d3b0511 100644 --- a/Shoko.Server/Databases/DatabaseFixes.cs +++ b/Shoko.Server/Databases/DatabaseFixes.cs @@ -6,6 +6,7 @@ using Shoko.Server.Repositories.Cached; using Shoko.Server.Repositories.Direct; using NLog; +using Pri.LongPath; using Shoko.Models; using Shoko.Models.Enums; using Shoko.Server.Models; @@ -75,7 +76,7 @@ public static void FixEmptyVideoInfos() if (p != null && !string.IsNullOrEmpty(p.FilePath) && v.Media != null) { v.FileName = p.FilePath; - int a = p.FilePath.LastIndexOf("\\", StringComparison.InvariantCulture); + int a = p.FilePath.LastIndexOf($"{Path.DirectorySeparatorChar}", StringComparison.InvariantCulture); if (a > 0) v.FileName = p.FilePath.Substring(a + 1); SVR_VideoLocal_Place.FillVideoInfoFromMedia(v, v.Media); diff --git a/Shoko.Server/Databases/MySQL.cs b/Shoko.Server/Databases/MySQL.cs index 4c32f3bf8..49d3fef65 100644 --- a/Shoko.Server/Databases/MySQL.cs +++ b/Shoko.Server/Databases/MySQL.cs @@ -691,6 +691,26 @@ public void BackupDatabase(string fullfilename) } } + public override bool TestConnection() + { + try + { + using (MySqlConnection conn = new MySqlConnection(GetConnectionString())) + { + var query = "select 1"; + MySqlCommand cmd = new MySqlCommand(query, conn); + conn.Open(); + cmd.ExecuteScalar(); + return true; + } + } + catch + { + // ignore + } + return false; + } + protected override Tuple ExecuteCommand(MySqlConnection connection, string command) { try diff --git a/Shoko.Server/Databases/SQLServer.cs b/Shoko.Server/Databases/SQLServer.cs index e7ea3f0f5..207466502 100644 --- a/Shoko.Server/Databases/SQLServer.cs +++ b/Shoko.Server/Databases/SQLServer.cs @@ -33,8 +33,7 @@ public void BackupDatabase(string fullfilename) fullfilename.Replace("'", "''") + "'"; - using (SqlConnection tmpConn = - new SqlConnection(GetConnectionString())) + using (SqlConnection tmpConn = new SqlConnection(GetConnectionString())) { tmpConn.Open(); @@ -46,6 +45,29 @@ public void BackupDatabase(string fullfilename) } } + public override bool TestConnection() + { + try + { + using (SqlConnection connection = new SqlConnection(GetConnectionString())) + { + var query = "select 1"; + + var command = new SqlCommand(query, connection); + + connection.Open(); + + command.ExecuteScalar(); + return true; + } + } + catch + { + // ignored + } + return false; + } + public override string GetConnectionString() { diff --git a/Shoko.Server/Extensions/ImageResolvers.TOBEMOVETOCOMMONS.cs b/Shoko.Server/Extensions/ImageResolvers.TOBEMOVETOCOMMONS.cs index 35bdb638e..845a3069f 100644 --- a/Shoko.Server/Extensions/ImageResolvers.TOBEMOVETOCOMMONS.cs +++ b/Shoko.Server/Extensions/ImageResolvers.TOBEMOVETOCOMMONS.cs @@ -26,7 +26,7 @@ public static string GetFullImagePath(this MovieDB_Fanart fanart) //strip out the base URL int pos = fanart.URL.IndexOf('/', 0); string fname = fanart.URL.Substring(pos + 1, fanart.URL.Length - pos - 1); - fname = fname.Replace("/", @"\"); + fname = fname.Replace("/", $"{System.IO.Path.DirectorySeparatorChar}"); return Path.Combine(ImageUtils.GetMovieDBImagePath(), fname); } @@ -37,7 +37,7 @@ public static string GetFullImagePath(this MovieDB_Poster movie) //strip out the base URL int pos = movie.URL.IndexOf('/', 0); string fname = movie.URL.Substring(pos + 1, movie.URL.Length - pos - 1); - fname = fname.Replace("/", @"\"); + fname = fname.Replace("/", $"{System.IO.Path.DirectorySeparatorChar}"); return System.IO.Path.Combine(ImageUtils.GetMovieDBImagePath(), fname); } @@ -46,7 +46,7 @@ public static string GetFullImagePath(this TvDB_Episode episode) if (String.IsNullOrEmpty(episode.Filename)) return string.Empty; string fname = episode.Filename; - fname = episode.Filename.Replace("/", @"\"); + fname = episode.Filename.Replace("/", $"{System.IO.Path.DirectorySeparatorChar}"); return System.IO.Path.Combine(ImageUtils.GetTvDBImagePath(), fname); } @@ -55,14 +55,14 @@ public static string GetFullImagePath(this TvDB_ImageFanart fanart) if (String.IsNullOrEmpty(fanart.BannerPath)) return string.Empty; string fname = fanart.BannerPath; - fname = fanart.BannerPath.Replace("/", @"\"); + fname = fanart.BannerPath.Replace("/", $"{System.IO.Path.DirectorySeparatorChar}"); return System.IO.Path.Combine(ImageUtils.GetTvDBImagePath(), fname); } public static string GetFullThumbnailPath(this TvDB_ImageFanart fanart) { string fname = fanart.ThumbnailPath; - fname = fanart.ThumbnailPath.Replace("/", @"\"); + fname = fanart.ThumbnailPath.Replace("/", $"{System.IO.Path.DirectorySeparatorChar}"); return System.IO.Path.Combine(ImageUtils.GetTvDBImagePath(), fname); } @@ -71,7 +71,7 @@ public static string GetFullImagePath(this TvDB_ImagePoster poster) if (String.IsNullOrEmpty(poster.BannerPath)) return string.Empty; string fname = poster.BannerPath; - fname = poster.BannerPath.Replace("/", @"\"); + fname = poster.BannerPath.Replace("/", $"{System.IO.Path.DirectorySeparatorChar}"); return System.IO.Path.Combine(ImageUtils.GetTvDBImagePath(), fname); } @@ -80,7 +80,7 @@ public static string GetFullImagePath(this TvDB_ImageWideBanner banner) if (String.IsNullOrEmpty(banner.BannerPath)) return string.Empty; string fname = banner.BannerPath; - fname = banner.BannerPath.Replace("/", @"\"); + fname = banner.BannerPath.Replace("/", $"{System.IO.Path.DirectorySeparatorChar}"); return System.IO.Path.Combine(ImageUtils.GetTvDBImagePath(), fname); } diff --git a/Shoko.Server/Extensions/ModelProviders.cs b/Shoko.Server/Extensions/ModelProviders.cs index 804a3d133..c1200ecb2 100644 --- a/Shoko.Server/Extensions/ModelProviders.cs +++ b/Shoko.Server/Extensions/ModelProviders.cs @@ -719,9 +719,9 @@ public static void Populate(this AniDB_Episode episode, Raw_AniDB_Episode epInfo episode.EpisodeNumber = epInfo.EpisodeNumber; episode.EpisodeType = epInfo.EpisodeType; episode.LengthSeconds = epInfo.LengthSeconds; - episode.Rating = epInfo.Rating.ToString(); + episode.Rating = epInfo.Rating.ToString(CultureInfo.InvariantCulture); episode.RomajiName = epInfo.RomajiName; - episode.Votes = epInfo.Votes.ToString(); + episode.Votes = epInfo.Votes.ToString(CultureInfo.InvariantCulture); } public static void Populate(this AniDB_GroupStatus grpstatus, Raw_AniDB_GroupStatus raw) diff --git a/Shoko.Server/Extensions/Utils.cs b/Shoko.Server/Extensions/Utils.cs old mode 100644 new mode 100755 index 79cf0c20b..04bb1a3c9 --- a/Shoko.Server/Extensions/Utils.cs +++ b/Shoko.Server/Extensions/Utils.cs @@ -3,7 +3,6 @@ using System.IO; using System.Linq; using System.Reflection; - using NutzCode.CloudFileSystem; using Shoko.Commons.Extensions; using Shoko.Models.Client; @@ -127,6 +126,5 @@ public static CL_AnimeGroup_User DeepCopy(this CL_AnimeGroup_User c) }; return contract; } - } } \ No newline at end of file diff --git a/Shoko.Server/FileHelper/Hasher.cs b/Shoko.Server/FileHelper/Hasher.cs index df5858b9a..f5e0e3a7a 100644 --- a/Shoko.Server/FileHelper/Hasher.cs +++ b/Shoko.Server/FileHelper/Hasher.cs @@ -64,7 +64,7 @@ static Hasher() { FileInfo fi = new FileInfo(fullexepath); fullexepath = Path.Combine(fi.Directory.FullName, Environment.Is64BitProcess ? "x64" : "x86", - "hasher.dll"); + "librhash.dll"); Finalise.ModuleHandle = LoadLibraryEx(fullexepath, IntPtr.Zero, 0); } } @@ -131,15 +131,13 @@ public static Hashes CalculateHashes(string strPath, OnHashProgress onHashProgre try { string filename = strPath.StartsWith(@"\\") ? strPath : @"\\?\" + strPath; //only prepend non-UNC paths (or paths that have this already) - rval = CalculateHashes_dll(filename, ref hash, onHashProgress, getCRC32, getMD5, getSHA1); - if (0 == rval) - { - rhash.ED2K = HashToString(hash, 0, 16); - if (!string.IsNullOrEmpty(rhash.ED2K)) gotHash = true; - if (getCRC32) rhash.CRC32 = HashToString(hash, 16, 4); - if (getMD5) rhash.MD5 = HashToString(hash, 20, 16); - if (getSHA1) rhash.SHA1 = HashToString(hash, 36, 20); - } + + (string e2Dk, string crc32, string md5, string sha1) = NativeHasher.GetHash(filename); + rhash.ED2K = e2Dk; + if (!string.IsNullOrEmpty(rhash.ED2K)) gotHash = true; + if (getCRC32) rhash.CRC32 = crc32; + if (getMD5) rhash.MD5 = md5; + if (getSHA1) rhash.SHA1 = sha1; } catch (Exception ex) { diff --git a/Shoko.Server/FileHelper/MD4Managed.cs b/Shoko.Server/FileHelper/MD4Managed.cs index 4c425aab7..1e08a6fea 100644 --- a/Shoko.Server/FileHelper/MD4Managed.cs +++ b/Shoko.Server/FileHelper/MD4Managed.cs @@ -21,7 +21,7 @@ protected MD4() { object obj = CryptoConfig.CreateFromName(hashName); // in case machine.config isn't configured to use any MD4 implementation - if (obj == null) + if (obj == null || Utils.IsRunningOnMono()) obj = new MD4Managed(); return (MD4) obj; diff --git a/Shoko.Server/FileHelper/MediaInfo/Convert.cs b/Shoko.Server/FileHelper/MediaInfo/Convert.cs index b36f0b367..3b264d4e9 100644 --- a/Shoko.Server/FileHelper/MediaInfo/Convert.cs +++ b/Shoko.Server/FileHelper/MediaInfo/Convert.cs @@ -722,10 +722,6 @@ public static Media Convert(string filename, IFile file) return m; } } - catch (Exception e) - { - throw new Exception(e.Message, e); - } finally { minstance?.Close(); diff --git a/Shoko.Server/FileHelper/MediaInfo/MediaInfoDLL.cs b/Shoko.Server/FileHelper/MediaInfo/MediaInfoDLL.cs old mode 100644 new mode 100755 index d82f717ac..2394c26e0 --- a/Shoko.Server/FileHelper/MediaInfo/MediaInfoDLL.cs +++ b/Shoko.Server/FileHelper/MediaInfo/MediaInfoDLL.cs @@ -1,534 +1,437 @@ -/* Copyright (c) MediaArea.net SARL. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license that can - * be found in the License.html file in the root of the source tree. - */ - -//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -// -// Microsoft Visual C# wrapper for MediaInfo Library -// See MediaInfo.h for help -// -// To make it working, you must put MediaInfo.Dll -// in the executable folder -// -//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -using System; -using System.Runtime.ExceptionServices; -using System.Runtime.InteropServices; -using System.Security; -using Pri.LongPath; - -#pragma warning disable 1591 // Disable XML documentation warnings - -namespace MediaInfoLib -{ - public enum StreamKind - { - General, - Video, - Audio, - Text, - Other, - Image, - Menu, - } - - public enum InfoKind - { - Name, - Text, - Measure, - Options, - NameText, - MeasureText, - Info, - HowTo - } - - public enum InfoOptions - { - ShowInInform, - Support, - ShowInSupported, - TypeOfValue - } - - public enum InfoFileOptions - { - FileOption_Nothing = 0x00, - FileOption_NoRecursive = 0x01, - FileOption_CloseAll = 0x02, - FileOption_Max = 0x04 - }; - - public enum Status - { - None = 0x00, - Accepted = 0x01, - Filled = 0x02, - Updated = 0x04, - Finalized = 0x08, - } - - public class MediaInfo : IDisposable - { - //Import of DLL functions. DO NOT USE until you know what you do (MediaInfo DLL do NOT use CoTaskMemAlloc to allocate memory) - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfo_New(); - - [DllImport("MediaInfo.dll")] - private static extern void MediaInfo_Delete(IntPtr Handle); - - [DllImport("MediaInfo.dll")] - [HandleProcessCorruptedStateExceptions] - [SecurityCritical] - private static extern IntPtr MediaInfo_Open(IntPtr Handle, [MarshalAs(UnmanagedType.LPWStr)] string FileName); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoA_Open(IntPtr Handle, IntPtr FileName); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfo_Open_Buffer_Init(IntPtr Handle, Int64 File_Size, Int64 File_Offset); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoA_Open(IntPtr Handle, Int64 File_Size, Int64 File_Offset); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfo_Open_Buffer_Continue(IntPtr Handle, IntPtr Buffer, IntPtr Buffer_Size); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoA_Open_Buffer_Continue(IntPtr Handle, Int64 File_Size, byte[] Buffer, - IntPtr Buffer_Size); - - [DllImport("MediaInfo.dll")] - private static extern Int64 MediaInfo_Open_Buffer_Continue_GoTo_Get(IntPtr Handle); - - [DllImport("MediaInfo.dll")] - private static extern Int64 MediaInfoA_Open_Buffer_Continue_GoTo_Get(IntPtr Handle); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfo_Open_Buffer_Finalize(IntPtr Handle); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoA_Open_Buffer_Finalize(IntPtr Handle); - - [DllImport("MediaInfo.dll")] - private static extern void MediaInfo_Close(IntPtr Handle); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfo_Inform(IntPtr Handle, IntPtr Reserved); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoA_Inform(IntPtr Handle, IntPtr Reserved); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfo_GetI(IntPtr Handle, IntPtr StreamKind, IntPtr StreamNumber, - IntPtr Parameter, IntPtr KindOfInfo); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoA_GetI(IntPtr Handle, IntPtr StreamKind, IntPtr StreamNumber, - IntPtr Parameter, IntPtr KindOfInfo); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfo_Get(IntPtr Handle, IntPtr StreamKind, IntPtr StreamNumber, - [MarshalAs(UnmanagedType.LPWStr)] string Parameter, IntPtr KindOfInfo, IntPtr KindOfSearch); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoA_Get(IntPtr Handle, IntPtr StreamKind, IntPtr StreamNumber, - IntPtr Parameter, IntPtr KindOfInfo, IntPtr KindOfSearch); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfo_Option(IntPtr Handle, [MarshalAs(UnmanagedType.LPWStr)] string Option, - [MarshalAs(UnmanagedType.LPWStr)] string Value); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoA_Option(IntPtr Handle, IntPtr Option, IntPtr Value); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfo_State_Get(IntPtr Handle); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfo_Count_Get(IntPtr Handle, IntPtr StreamKind, IntPtr StreamNumber); - - #region Changes from original mediainfo that handle automatic loading the correct library depending of the os used x86/x64 - - [System.Flags] - internal enum LoadLibraryFlags : uint - { - DONT_RESOLVE_DLL_REFERENCES = 0x00000001, - LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010, - LOAD_LIBRARY_AS_DATAFILE = 0x00000002, - LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040, - LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020, - LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008 - } - - [DllImport("kernel32.dll")] - internal static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool FreeLibrary(IntPtr hModule); - - private static System.IntPtr moduleHandle = IntPtr.Zero; - private static System.IntPtr curlHandle = IntPtr.Zero; - - //MediaInfo class - public MediaInfo() - { - if (moduleHandle == IntPtr.Zero) - { - string fullexepath = System.Reflection.Assembly.GetEntryAssembly().Location; - if (!string.IsNullOrEmpty(fullexepath)) - { - FileInfo fi = new FileInfo(fullexepath); - fullexepath = Path.Combine(fi.Directory.FullName, Environment.Is64BitProcess ? "x64" : "x86", - "MediaInfo.dll"); - string curlpath = Path.Combine(fi.Directory.FullName, Environment.Is64BitProcess ? "x64" : "x86", - "libcurl.dll"); - NLog.LogManager.GetCurrentClassLogger() - .Trace(string.Format("Using MediaInfo at: {0}", fullexepath)); - - moduleHandle = LoadLibraryEx(fullexepath, IntPtr.Zero, 0); - curlHandle = LoadLibraryEx(curlpath, IntPtr.Zero, 0); - } - } - try - { - Handle = MediaInfo_New(); - } - catch - { - Handle = (IntPtr) 0; - } - MustUseAnsi = Environment.OSVersion.ToString().IndexOf("Windows") == -1; - } - - ~MediaInfo() - { - - } - - public void Dispose() - { - if (Handle != IntPtr.Zero) - MediaInfo_Delete(Handle); - if (moduleHandle != IntPtr.Zero) - { - FreeLibrary(moduleHandle); - moduleHandle = IntPtr.Zero; - } - if (curlHandle != IntPtr.Zero) - { - FreeLibrary(curlHandle); - curlHandle = IntPtr.Zero; - } - } - - #endregion - - [HandleProcessCorruptedStateExceptions] - [SecurityCritical] - public int Open(String FileName) - { - if (Handle == (IntPtr) 0) - return 0; - - if (!Shoko.Server.Utils.IsLinux) - FileName = FileName.StartsWith(@"\\") ? FileName : @"\\?\" + FileName; // add long path prefix if not running on linux, and not a unc path. - - if (MustUseAnsi) - { - IntPtr FileName_Ptr = Marshal.StringToHGlobalAnsi(FileName); - int ToReturn = (int) MediaInfoA_Open(Handle, FileName_Ptr); - Marshal.FreeHGlobal(FileName_Ptr); - return ToReturn; - } - try - { - return (int) MediaInfo_Open(Handle, FileName); - } - catch (Exception) - { - return 0; - //Ignored - } - } - - public int Open_Buffer_Init(Int64 File_Size, Int64 File_Offset) - { - if (Handle == (IntPtr) 0) return 0; - return (int) MediaInfo_Open_Buffer_Init(Handle, File_Size, File_Offset); - } - - public int Open_Buffer_Continue(IntPtr Buffer, IntPtr Buffer_Size) - { - if (Handle == (IntPtr) 0) return 0; - return (int) MediaInfo_Open_Buffer_Continue(Handle, Buffer, Buffer_Size); - } - - public Int64 Open_Buffer_Continue_GoTo_Get() - { - if (Handle == (IntPtr) 0) return 0; - return MediaInfo_Open_Buffer_Continue_GoTo_Get(Handle); - } - - public int Open_Buffer_Finalize() - { - if (Handle == (IntPtr) 0) return 0; - return (int) MediaInfo_Open_Buffer_Finalize(Handle); - } - - public void Close() - { - if (Handle == (IntPtr) 0) return; - MediaInfo_Close(Handle); - } - - public String Inform() - { - if (Handle == (IntPtr) 0) - return "Unable to load MediaInfo library"; - if (MustUseAnsi) - return Marshal.PtrToStringAnsi(MediaInfoA_Inform(Handle, (IntPtr) 0)); - else - return Marshal.PtrToStringUni(MediaInfo_Inform(Handle, (IntPtr) 0)); - } - - public String Get(StreamKind StreamKind, int StreamNumber, String Parameter, InfoKind KindOfInfo, - InfoKind KindOfSearch) - { - if (Handle == (IntPtr) 0) - return "Unable to load MediaInfo library"; - if (MustUseAnsi) - { - IntPtr Parameter_Ptr = Marshal.StringToHGlobalAnsi(Parameter); - String ToReturn = - Marshal.PtrToStringAnsi(MediaInfoA_Get(Handle, (IntPtr) StreamKind, (IntPtr) StreamNumber, - Parameter_Ptr, (IntPtr) KindOfInfo, (IntPtr) KindOfSearch)); - Marshal.FreeHGlobal(Parameter_Ptr); - return ToReturn; - } - else - return - Marshal.PtrToStringUni(MediaInfo_Get(Handle, (IntPtr) StreamKind, (IntPtr) StreamNumber, Parameter, - (IntPtr) KindOfInfo, (IntPtr) KindOfSearch)); - } - - public String Get(StreamKind StreamKind, int StreamNumber, int Parameter, InfoKind KindOfInfo) - { - if (Handle == (IntPtr) 0) - return "Unable to load MediaInfo library"; - if (MustUseAnsi) - return - Marshal.PtrToStringAnsi(MediaInfoA_GetI(Handle, (IntPtr) StreamKind, (IntPtr) StreamNumber, - (IntPtr) Parameter, (IntPtr) KindOfInfo)); - else - return - Marshal.PtrToStringUni(MediaInfo_GetI(Handle, (IntPtr) StreamKind, (IntPtr) StreamNumber, - (IntPtr) Parameter, (IntPtr) KindOfInfo)); - } - - public String Option(String Option, String Value) - { - if (Handle == (IntPtr) 0) - return "Unable to load MediaInfo library"; - if (MustUseAnsi) - { - IntPtr Option_Ptr = Marshal.StringToHGlobalAnsi(Option); - IntPtr Value_Ptr = Marshal.StringToHGlobalAnsi(Value); - String ToReturn = Marshal.PtrToStringAnsi(MediaInfoA_Option(Handle, Option_Ptr, Value_Ptr)); - Marshal.FreeHGlobal(Option_Ptr); - Marshal.FreeHGlobal(Value_Ptr); - return ToReturn; - } - else - return Marshal.PtrToStringUni(MediaInfo_Option(Handle, Option, Value)); - } - - public int State_Get() - { - if (Handle == (IntPtr) 0) return 0; - return (int) MediaInfo_State_Get(Handle); - } - - public int Count_Get(StreamKind StreamKind, int StreamNumber) - { - if (Handle == (IntPtr) 0) return 0; - return (int) MediaInfo_Count_Get(Handle, (IntPtr) StreamKind, (IntPtr) StreamNumber); - } - - private IntPtr Handle; - private bool MustUseAnsi; - - //Default values, if you know how to set default values in C#, say me - public String Get(StreamKind StreamKind, int StreamNumber, String Parameter, InfoKind KindOfInfo) - { - return Get(StreamKind, StreamNumber, Parameter, KindOfInfo, InfoKind.Name); - } - - public String Get(StreamKind StreamKind, int StreamNumber, String Parameter) - { - return Get(StreamKind, StreamNumber, Parameter, InfoKind.Text, InfoKind.Name); - } - - public String Get(StreamKind StreamKind, int StreamNumber, int Parameter) - { - return Get(StreamKind, StreamNumber, Parameter, InfoKind.Text); - } - - public String Option(String Option_) - { - return Option(Option_, string.Empty); - } - - public int Count_Get(StreamKind StreamKind) - { - return Count_Get(StreamKind, -1); - } - } - - - public class MediaInfoList : IDisposable - { - //Import of DLL functions. DO NOT USE until you know what you do (MediaInfo DLL do NOT use CoTaskMemAlloc to allocate memory) - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoList_New(); - - [DllImport("MediaInfo.dll")] - private static extern void MediaInfoList_Delete(IntPtr Handle); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoList_Open(IntPtr Handle, - [MarshalAs(UnmanagedType.LPWStr)] string FileName, - IntPtr Options); - - [DllImport("MediaInfo.dll")] - private static extern void MediaInfoList_Close(IntPtr Handle, IntPtr FilePos); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoList_Inform(IntPtr Handle, IntPtr FilePos, IntPtr Reserved); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoList_GetI(IntPtr Handle, IntPtr FilePos, IntPtr StreamKind, - IntPtr StreamNumber, IntPtr Parameter, IntPtr KindOfInfo); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoList_Get(IntPtr Handle, IntPtr FilePos, IntPtr StreamKind, - IntPtr StreamNumber, [MarshalAs(UnmanagedType.LPWStr)] string Parameter, IntPtr KindOfInfo, - IntPtr KindOfSearch); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoList_Option(IntPtr Handle, - [MarshalAs(UnmanagedType.LPWStr)] string Option, - [MarshalAs(UnmanagedType.LPWStr)] string Value); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoList_State_Get(IntPtr Handle); - - [DllImport("MediaInfo.dll")] - private static extern IntPtr MediaInfoList_Count_Get(IntPtr Handle, IntPtr FilePos, IntPtr StreamKind, - IntPtr StreamNumber); - - //MediaInfo class - public MediaInfoList() - { - Handle = MediaInfoList_New(); - } - - ~MediaInfoList() - { - MediaInfoList_Delete(Handle); - } - - public int Open(String FileName, InfoFileOptions Options) - { - return (int) MediaInfoList_Open(Handle, FileName, (IntPtr) Options); - } - - public void Close(int FilePos) - { - MediaInfoList_Close(Handle, (IntPtr) FilePos); - } - - public String Inform(int FilePos) - { - return Marshal.PtrToStringUni(MediaInfoList_Inform(Handle, (IntPtr) FilePos, (IntPtr) 0)); - } - - public String Get(int FilePos, StreamKind StreamKind, int StreamNumber, String Parameter, InfoKind KindOfInfo, - InfoKind KindOfSearch) - { - return - Marshal.PtrToStringUni(MediaInfoList_Get(Handle, (IntPtr) FilePos, (IntPtr) StreamKind, - (IntPtr) StreamNumber, Parameter, (IntPtr) KindOfInfo, (IntPtr) KindOfSearch)); - } - - public String Get(int FilePos, StreamKind StreamKind, int StreamNumber, int Parameter, InfoKind KindOfInfo) - { - return - Marshal.PtrToStringUni(MediaInfoList_GetI(Handle, (IntPtr) FilePos, (IntPtr) StreamKind, - (IntPtr) StreamNumber, (IntPtr) Parameter, (IntPtr) KindOfInfo)); - } - - public String Option(String Option, String Value) - { - return Marshal.PtrToStringUni(MediaInfoList_Option(Handle, Option, Value)); - } - - public int State_Get() - { - return (int) MediaInfoList_State_Get(Handle); - } - - public int Count_Get(int FilePos, StreamKind StreamKind, int StreamNumber) - { - return (int) MediaInfoList_Count_Get(Handle, (IntPtr) FilePos, (IntPtr) StreamKind, (IntPtr) StreamNumber); - } - - private IntPtr Handle; - - //Default values, if you know how to set default values in C#, say me - public void Open(String FileName) - { - Open(FileName, 0); - } - - public void Close() - { - Close(-1); - } - - public String Get(int FilePos, StreamKind StreamKind, int StreamNumber, String Parameter, InfoKind KindOfInfo) - { - return Get(FilePos, StreamKind, StreamNumber, Parameter, KindOfInfo, InfoKind.Name); - } - - public String Get(int FilePos, StreamKind StreamKind, int StreamNumber, String Parameter) - { - return Get(FilePos, StreamKind, StreamNumber, Parameter, InfoKind.Text, InfoKind.Name); - } - - public String Get(int FilePos, StreamKind StreamKind, int StreamNumber, int Parameter) - { - return Get(FilePos, StreamKind, StreamNumber, Parameter, InfoKind.Text); - } - - public String Option(String Option_) - { - return Option(Option_, string.Empty); - } - - public int Count_Get(int FilePos, StreamKind StreamKind) - { - return Count_Get(FilePos, StreamKind, -1); - } - - public void Dispose() - { - - } - } +/* Copyright (c) MediaArea.net SARL. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license that can + * be found in the License.html file in the root of the source tree. + */ + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Microsoft Visual C# wrapper for MediaInfo Library +// See MediaInfo.h for help +// +// To make it working, you must put MediaInfo.Dll +// in the executable folder +// +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using NLog; +using FileInfo = Pri.LongPath.FileInfo; +using Path = Pri.LongPath.Path; + +namespace MediaInfoLib +{ + [Flags] + public enum BufferStatus + { + Accepted = 1, + Filled = 2, + Updated = 4, + Finalized = 8 + } + + public enum StreamKind + { + General, + Video, + Audio, + Text, + Other, + Image, + Menu + } + + public enum InfoKind + { + Name, + Text, + Measure, + Options, + NameText, + MeasureText, + Info, + HowTo + } + + public enum InfoOptions + { + ShowInInform, + Support, + ShowInSupported, + TypeOfValue + } + + public enum InfoFileOptions + { + FileOption_Nothing = 0x00, + FileOption_NoRecursive = 0x01, + FileOption_CloseAll = 0x02, + FileOption_Max = 0x04 + }; + + + public class MediaInfo : IDisposable + { + [System.Flags] + internal enum LoadLibraryFlags : uint + { + DONT_RESOLVE_DLL_REFERENCES = 0x00000001, + LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010, + LOAD_LIBRARY_AS_DATAFILE = 0x00000002, + LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040, + LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020, + LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008 + } + + [DllImport("kernel32.dll")] + internal static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool FreeLibrary(IntPtr hModule); + + private static readonly Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + private IntPtr _handle; + + public bool MustUseAnsi { get; set; } + public Encoding Encoding { get; set; } + private static System.IntPtr moduleHandle = IntPtr.Zero; + private static System.IntPtr curlHandle = IntPtr.Zero; + + + public MediaInfo() + { + + if ((_handle == IntPtr.Zero) && !Shoko.Server.Utils.IsRunningOnMono()) + { + string fullexepath = System.Reflection.Assembly.GetEntryAssembly().Location; + if (!string.IsNullOrEmpty(fullexepath)) + { + FileInfo fi = new FileInfo(fullexepath); + fullexepath = Path.Combine(fi.Directory.FullName, Environment.Is64BitProcess ? "x64" : "x86", + "MediaInfo.dll"); + string curlpath = Path.Combine(fi.Directory.FullName, Environment.Is64BitProcess ? "x64" : "x86", + "libcurl.dll"); + + Logger.Info("Using MediaInfo at: {0}", fullexepath); + + moduleHandle = LoadLibraryEx(fullexepath, IntPtr.Zero, 0); + curlHandle = LoadLibraryEx(curlpath, IntPtr.Zero, 0); + if (moduleHandle == IntPtr.Zero) throw new FileNotFoundException("Unable to load MediaInfo.dll"); + } + } + + try + { + _handle = MediaInfo_New(); + } + catch + { + _handle = (IntPtr)0; + } + + InitializeEncoding(); + } + + ~MediaInfo() + { + if (_handle != IntPtr.Zero) + { + MediaInfo_Delete(_handle); + } + if (moduleHandle != IntPtr.Zero) + { + FreeLibrary(moduleHandle); + moduleHandle = IntPtr.Zero; + } + if (curlHandle != IntPtr.Zero) + { + FreeLibrary(curlHandle); + curlHandle = IntPtr.Zero; + } + } + + public void Dispose() + { + if (_handle != IntPtr.Zero) + { + MediaInfo_Delete(_handle); + } + GC.SuppressFinalize(this); + } + + private void InitializeEncoding() + { + if (Environment.OSVersion.ToString().IndexOf("Windows") != -1) + { + // Windows guaranteed UCS-2 + MustUseAnsi = false; + Encoding = Encoding.Unicode; + } + else + { + // Linux normally UCS-4. As fallback we try UCS-2 and plain Ansi. + MustUseAnsi = false; + Encoding = Encoding.UTF32; + + if (Option("Info_Version", "").StartsWith("MediaInfoLib")) + { + return; + } + + Encoding = Encoding.Unicode; + + if (Option("Info_Version", "").StartsWith("MediaInfoLib")) + { + return; + } + + MustUseAnsi = true; + Encoding = Encoding.Default; + + if (Option("Info_Version", "").StartsWith("MediaInfoLib")) + { + return; + } + + throw new NotSupportedException("Unsupported MediaInfoLib encoding"); + } + } + + private IntPtr MakeStringParameter(string value) + { + var buffer = Encoding.GetBytes(value); + + Array.Resize(ref buffer, buffer.Length + 4); + + var buf = Marshal.AllocHGlobal(buffer.Length); + Marshal.Copy(buffer, 0, buf, buffer.Length); + + return buf; + } + + private string MakeStringResult(IntPtr value) + { + if (Encoding == Encoding.Unicode) + { + return Marshal.PtrToStringUni(value); + } + else if (Encoding == Encoding.UTF32) + { + int i = 0; + for (; i < 1024; i += 4) + { + var data = Marshal.ReadInt32(value, i); + if (data == 0) + { + break; + } + } + + var buffer = new byte[i]; + Marshal.Copy(value, buffer, 0, i); + + return Encoding.GetString(buffer, 0, i); + } + else + { + return Marshal.PtrToStringAnsi(value); + } + } + + public int Open(string fileName) + { + if (!Shoko.Server.Utils.IsLinux) + fileName = fileName.StartsWith(@"\\") ? fileName : @"\\?\" + fileName; // add long path prefix if not running on linux, and not a unc path. + + var pFileName = MakeStringParameter(fileName); + try + { + if (MustUseAnsi) + { + return (int)MediaInfoA_Open(_handle, pFileName); + } + else + { + return (int)MediaInfo_Open(_handle, pFileName); + } + } + finally + { + Marshal.FreeHGlobal(pFileName); + } + } + + public int Open(System.IO.Stream stream) + { + if (stream.Length < 1024) + { + return 0; + } + + var isValid = (int)MediaInfo_Open_Buffer_Init(_handle, stream.Length, 0); + if (isValid == 1) + { + var buffer = new byte[16 * 1024]; + long seekStart = 0; + long totalRead = 0; + int bufferRead; + + do + { + bufferRead = stream.Read(buffer, 0, buffer.Length); + totalRead += bufferRead; + + var status = (BufferStatus)MediaInfo_Open_Buffer_Continue(_handle, buffer, (IntPtr)bufferRead); + + if (status.HasFlag(BufferStatus.Finalized) || status <= 0 || bufferRead == 0) + { + Logger.Trace("Read file offset {0}-{1} ({2} bytes)", seekStart, stream.Position, stream.Position - seekStart); + break; + } + + var seekPos = MediaInfo_Open_Buffer_Continue_GoTo_Get(_handle); + if (seekPos != -1) + { + Logger.Trace("Read file offset {0}-{1} ({2} bytes)", seekStart, stream.Position, stream.Position - seekStart); + seekPos = stream.Seek(seekPos, System.IO.SeekOrigin.Begin); + seekStart = seekPos; + MediaInfo_Open_Buffer_Init(_handle, stream.Length, seekPos); + } + } while (bufferRead > 0); + + MediaInfo_Open_Buffer_Finalize(_handle); + + Logger.Trace("Read a total of {0} bytes ({1:0.0}%)", totalRead, totalRead * 100.0 / stream.Length); + } + + return isValid; + } + + public void Close() + { + MediaInfo_Close(_handle); + } + + public string Get(StreamKind streamKind, int streamNumber, string parameter, InfoKind infoKind = InfoKind.Text, InfoKind searchKind = InfoKind.Name) + { + var pParameter = MakeStringParameter(parameter); + try + { + if (MustUseAnsi) + { + return MakeStringResult(MediaInfoA_Get(_handle, (IntPtr)streamKind, (IntPtr)streamNumber, pParameter, (IntPtr)infoKind, (IntPtr)searchKind)); + } + else + { + return MakeStringResult(MediaInfo_Get(_handle, (IntPtr)streamKind, (IntPtr)streamNumber, pParameter, (IntPtr)infoKind, (IntPtr)searchKind)); + } + } + finally + { + Marshal.FreeHGlobal(pParameter); + } + } + + public string Get(StreamKind streamKind, int streamNumber, int parameter, InfoKind infoKind) + { + if (MustUseAnsi) + { + return MakeStringResult(MediaInfoA_GetI(_handle, (IntPtr)streamKind, (IntPtr)streamNumber, (IntPtr)parameter, (IntPtr)infoKind)); + } + else + { + return MakeStringResult(MediaInfo_GetI(_handle, (IntPtr)streamKind, (IntPtr)streamNumber, (IntPtr)parameter, (IntPtr)infoKind)); + } + } + + public string Option(string option, string value) + { + var pOption = MakeStringParameter(option); + var pValue = MakeStringParameter(value); + try + { + if (MustUseAnsi) + { + return MakeStringResult(MediaInfoA_Option(_handle, pOption, pValue)); + } + else + { + return MakeStringResult(MediaInfo_Option(_handle, pOption, pValue)); + } + } + finally + { + Marshal.FreeHGlobal(pOption); + Marshal.FreeHGlobal(pValue); + } + } + + public int State_Get() + { + return (int)MediaInfo_State_Get(_handle); + } + + public int Count_Get(StreamKind streamKind, int streamNumber = -1) + { + return (int)MediaInfo_Count_Get(_handle, (IntPtr)streamKind, (IntPtr)streamNumber); + } + + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfo_New(); + [DllImport("MediaInfo.dll")] + private static extern void MediaInfo_Delete(IntPtr handle); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfo_Open(IntPtr handle, IntPtr fileName); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfo_Open_Buffer_Init(IntPtr handle, long fileSize, long fileOffset); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfo_Open_Buffer_Continue(IntPtr handle, byte[] buffer, IntPtr bufferSize); + [DllImport("MediaInfo.dll")] + private static extern long MediaInfo_Open_Buffer_Continue_GoTo_Get(IntPtr handle); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfo_Open_Buffer_Finalize(IntPtr handle); + [DllImport("MediaInfo.dll")] + private static extern void MediaInfo_Close(IntPtr handle); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfo_GetI(IntPtr handle, IntPtr streamKind, IntPtr streamNumber, IntPtr parameter, IntPtr infoKind); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfo_Get(IntPtr handle, IntPtr streamKind, IntPtr streamNumber, IntPtr parameter, IntPtr infoKind, IntPtr searchKind); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfo_Option(IntPtr handle, IntPtr option, IntPtr value); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfo_State_Get(IntPtr handle); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfo_Count_Get(IntPtr handle, IntPtr StreamKind, IntPtr streamNumber); + + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfoA_New(); + [DllImport("MediaInfo.dll")] + private static extern void MediaInfoA_Delete(IntPtr handle); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfoA_Open(IntPtr handle, IntPtr fileName); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfoA_Open_Buffer_Init(IntPtr handle, long fileSize, long fileOffset); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfoA_Open_Buffer_Continue(IntPtr handle, byte[] buffer, IntPtr bufferSize); + [DllImport("MediaInfo.dll")] + private static extern long MediaInfoA_Open_Buffer_Continue_GoTo_Get(IntPtr handle); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfoA_Open_Buffer_Finalize(IntPtr handle); + [DllImport("MediaInfo.dll")] + private static extern void MediaInfoA_Close(IntPtr handle); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfoA_GetI(IntPtr handle, IntPtr streamKind, IntPtr streamNumber, IntPtr parameter, IntPtr infoKind); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfoA_Get(IntPtr handle, IntPtr streamKind, IntPtr streamNumber, IntPtr parameter, IntPtr infoKind, IntPtr searchKind); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfoA_Option(IntPtr handle, IntPtr option, IntPtr value); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfoA_State_Get(IntPtr handle); + [DllImport("MediaInfo.dll")] + private static extern IntPtr MediaInfoA_Count_Get(IntPtr handle, IntPtr StreamKind, IntPtr streamNumber); + } } \ No newline at end of file diff --git a/Shoko.Server/FileHelper/NativeHasher.cs b/Shoko.Server/FileHelper/NativeHasher.cs new file mode 100644 index 000000000..62652bded --- /dev/null +++ b/Shoko.Server/FileHelper/NativeHasher.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using File = Pri.LongPath.File; + +namespace Shoko.Server.FileHelper +{ + class NativeHasher + { + public static (string e2dk, string crc32, string md5, string sha1) GetHash(string filename) + { + StringBuilder sb = new StringBuilder(); + Native.rhash_library_init(); + IntPtr ctx = Native.rhash_init(RHashIds.RHASH_ED2K | RHashIds.RHASH_CRC32 | RHashIds.RHASH_MD5 | + RHashIds.RHASH_SHA1); + + string e2dk = "", crc32 = "", md5 = "", sha1 = ""; + + using (Stream source = File.OpenRead(filename)) + { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) + { + var buf = Marshal.AllocHGlobal(bytesRead); + Marshal.Copy(buffer, 0, buf, bytesRead); + Native.rhash_update(ctx, buf, bytesRead); + Marshal.FreeHGlobal(buf); + } + } + + IntPtr output = Marshal.AllocHGlobal(200); + + Native.rhash_print(output, ctx, RHashIds.RHASH_ED2K, RhashPrintSumFlags.RHPR_DEFAULT); + e2dk = Marshal.PtrToStringAnsi(output); + + Native.rhash_print(output, ctx, RHashIds.RHASH_CRC32, RhashPrintSumFlags.RHPR_DEFAULT); + crc32 = Marshal.PtrToStringAnsi(output); + + Native.rhash_print(output, ctx, RHashIds.RHASH_MD5, RhashPrintSumFlags.RHPR_DEFAULT); + md5 = Marshal.PtrToStringAnsi(output); + + Native.rhash_print(output, ctx, RHashIds.RHASH_SHA1, RhashPrintSumFlags.RHPR_DEFAULT); + sha1 = Marshal.PtrToStringAnsi(output); + + Marshal.FreeHGlobal(output); + + Native.rhash_final(ctx, IntPtr.Zero); + Native.rhash_free(ctx); + + return (e2dk, crc32, md5, sha1); + } + + + private static class Native + { + private const string Lib = "librhash"; + + [DllImport(Lib, CallingConvention = CallingConvention.Cdecl)] + internal static extern void rhash_library_init(); + + [DllImport(Lib, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr rhash_init(RHashIds ids); + + [DllImport(Lib, CallingConvention = CallingConvention.Cdecl)] + internal static extern int rhash_update(IntPtr ctx, IntPtr data, int count); + + [DllImport(Lib, CallingConvention = CallingConvention.Cdecl)] + internal static extern int rhash_final(IntPtr ctx, IntPtr result); + + [DllImport(Lib, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + internal static extern int rhash_print(IntPtr outStr, IntPtr ctx, RHashIds hashId, RhashPrintSumFlags flags); + + [DllImport(Lib, CallingConvention = CallingConvention.Cdecl)] + internal static extern void rhash_free(IntPtr ctx); + } + + [Flags] + private enum RhashPrintSumFlags + { + /** print in a default format */ + RHPR_DEFAULT = 0x0, + /** output as binary message digest */ + RHPR_RAW = 0x1, + /** print as a hexadecimal string */ + RHPR_HEX = 0x2, + /** print as a base32-encoded string */ + RHPR_BASE32 = 0x3, + /** print as a base64-encoded string */ + RHPR_BASE64 = 0x4, + + /** + * Print as an uppercase string. Can be used + * for base32 or hexadecimal format only. + */ + RHPR_UPPERCASE = 0x8, + + /** + * Reverse hash bytes. Can be used for GOST hash. + */ + RHPR_REVERSE = 0x10, + + /** don't print 'magnet:?' prefix in rhash_print_magnet */ + RHPR_NO_MAGNET = 0x20, + /** print file size in rhash_print_magnet */ + RHPR_FILESIZE = 0x40, + }; + + [Flags] + private enum RHashIds + { + RHASH_CRC32 = 0x01, + RHASH_MD4 = 0x02, + RHASH_MD5 = 0x04, + RHASH_SHA1 = 0x08, + RHASH_TIGER = 0x10, + RHASH_TTH = 0x20, + RHASH_BTIH = 0x40, + RHASH_ED2K = 0x80, + RHASH_AICH = 0x100, + RHASH_WHIRLPOOL = 0x200, + RHASH_RIPEMD160 = 0x400, + RHASH_GOST = 0x800, + RHASH_GOST_CRYPTOPRO = 0x1000, + RHASH_HAS160 = 0x2000, + RHASH_SNEFRU128 = 0x4000, + RHASH_SNEFRU256 = 0x8000, + RHASH_SHA224 = 0x10000, + RHASH_SHA256 = 0x20000, + RHASH_SHA384 = 0x40000, + RHASH_SHA512 = 0x80000, + RHASH_EDONR256 = 0x0100000, + RHASH_EDONR512 = 0x0200000, + RHASH_SHA3_224 = 0x0400000, + RHASH_SHA3_256 = 0x0800000, + RHASH_SHA3_384 = 0x1000000, + RHASH_SHA3_512 = 0x2000000, + + /** The bit-mask containing all supported hashe functions */ + RHASH_ALL_HASHES = RHASH_CRC32 | RHASH_MD4 | RHASH_MD5 | RHASH_ED2K | RHASH_SHA1 | + RHASH_TIGER | RHASH_TTH | RHASH_GOST | RHASH_GOST_CRYPTOPRO | + RHASH_BTIH | RHASH_AICH | RHASH_WHIRLPOOL | RHASH_RIPEMD160 | + RHASH_HAS160 | RHASH_SNEFRU128 | RHASH_SNEFRU256 | + RHASH_SHA224 | RHASH_SHA256 | RHASH_SHA384 | RHASH_SHA512 | + RHASH_SHA3_224 | RHASH_SHA3_256 | RHASH_SHA3_384 | RHASH_SHA3_512 | + RHASH_EDONR256 | RHASH_EDONR512, + + /** The number of supported hash functions */ + RHASH_HASH_COUNT = 26 + }; + } +} diff --git a/Shoko.Server/Import/Importer.cs b/Shoko.Server/Import/Importer.cs old mode 100644 new mode 100755 diff --git a/Shoko.Server/MigrationDirectory.cs b/Shoko.Server/MigrationDirectory.cs old mode 100644 new mode 100755 diff --git a/Shoko.Server/Models/SVR_AniDB_Anime.cs b/Shoko.Server/Models/SVR_AniDB_Anime.cs index 2b5f61bdd..2e0ecfefa 100644 --- a/Shoko.Server/Models/SVR_AniDB_Anime.cs +++ b/Shoko.Server/Models/SVR_AniDB_Anime.cs @@ -904,7 +904,7 @@ private bool Populate(Raw_AniDB_Anime animeInfo) return true; } - public void PopulateAndSaveFromHTTP(ISession session, Raw_AniDB_Anime animeInfo, List eps, + public bool PopulateAndSaveFromHTTP(ISession session, Raw_AniDB_Anime animeInfo, List eps, List titles, List cats, List tags, List chars, List rels, List sims, @@ -923,7 +923,7 @@ public void PopulateAndSaveFromHTTP(ISession session, Raw_AniDB_Anime animeInfo, "This is not an error on our end. It is AniDB's issue, " + "as they did not return either an ID or a title for the anime."); totalTimer.Stop(); - return; + return false; } // save now for FK purposes @@ -970,6 +970,7 @@ public void PopulateAndSaveFromHTTP(ISession session, Raw_AniDB_Anime animeInfo, totalTimer.Stop(); logger.Trace("TOTAL TIME in : " + totalTimer.ElapsedMilliseconds); logger.Trace("------------------------------------------------"); + return true; } /// diff --git a/Shoko.Server/Models/SVR_AnimeGroup.cs b/Shoko.Server/Models/SVR_AnimeGroup.cs index 756917d24..73b32fabb 100644 --- a/Shoko.Server/Models/SVR_AnimeGroup.cs +++ b/Shoko.Server/Models/SVR_AnimeGroup.cs @@ -580,22 +580,6 @@ public GroupVotes GetVotes(ISessionWrapper session) return votes ?? GroupVotes.Null; } - /* - public string TagsString - { - get - { - string temp = string.Empty; - foreach (AniDB_Tag tag in Tags) - temp += tag.TagName + "|"; - if (temp.Length > 2) - temp = temp.Substring(0, temp.Length - 2); - - return temp; - } - } - */ - public List Tags { get @@ -604,49 +588,27 @@ public List Tags List animeTagIDs = new List(); List animeTags = new List(); - using (var session = DatabaseFactory.SessionFactory.OpenSession()) + foreach (SVR_AnimeSeries ser in GetAllSeries()) { - // get a list of all the unique tags for this all the series in this group - foreach (SVR_AnimeSeries ser in GetAllSeries()) + foreach (AniDB_Anime_Tag aac in ser.GetAnime().GetAnimeTags()) { - foreach (AniDB_Anime_Tag aac in ser.GetAnime().GetAnimeTags()) + if (!animeTagIDs.Contains(aac.AniDB_Anime_TagID)) { - if (!animeTagIDs.Contains(aac.AniDB_Anime_TagID)) - { - animeTagIDs.Add(aac.AniDB_Anime_TagID); - animeTags.Add(aac); - } + animeTagIDs.Add(aac.AniDB_Anime_TagID); + animeTags.Add(aac); } } - - foreach (AniDB_Anime_Tag animeTag in animeTags.OrderByDescending(a => a.Weight)) - { - AniDB_Tag tag = RepoFactory.AniDB_Tag.GetByTagID(animeTag.TagID); - if (tag != null) tags.Add(tag); - } } - return tags; - } - } - - /* - public string CustomTagsString - { - get - { - string temp = string.Empty; - foreach (CustomTag tag in CustomTags) + foreach (AniDB_Anime_Tag animeTag in animeTags.OrderByDescending(a => a.Weight)) { - if (!string.IsNullOrEmpty(temp)) - temp += "|"; - temp += tag.TagName; + AniDB_Tag tag = RepoFactory.AniDB_Tag.GetByTagID(animeTag.TagID); + if (tag != null) tags.Add(tag); } - - return temp; + + return tags; } } - */ public List CustomTags { @@ -1390,10 +1352,8 @@ public void UpdateGroupFilters(HashSet types, SVR_JMMU } } } - foreach (SVR_GroupFilter gf in tosave) - { - RepoFactory.GroupFilter.Save(gf); - } + + RepoFactory.GroupFilter.Save(tosave); } diff --git a/Shoko.Server/Models/SVR_AnimeSeries.cs b/Shoko.Server/Models/SVR_AnimeSeries.cs index a6698bbec..7a3b795da 100644 --- a/Shoko.Server/Models/SVR_AnimeSeries.cs +++ b/Shoko.Server/Models/SVR_AnimeSeries.cs @@ -63,7 +63,10 @@ public void CollectContractMemory() } - public string Year => GetAnime().GetYear(); + public string Year + { + get { return GetAnime().GetYear(); } + } private static Logger logger = LogManager.GetCurrentClassLogger(); diff --git a/Shoko.Server/Models/SVR_CloudAccount.cs b/Shoko.Server/Models/SVR_CloudAccount.cs old mode 100644 new mode 100755 index e41f63490..42cb67ca1 --- a/Shoko.Server/Models/SVR_CloudAccount.cs +++ b/Shoko.Server/Models/SVR_CloudAccount.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Web.Script.Serialization; - using System.Xml.Serialization; using Newtonsoft.Json; using NutzCode.CloudFileSystem; @@ -66,15 +65,21 @@ public IFileSystem FileSystem } } - public bool IsConnected => ServerState.Instance.ConnectedFileSystems.ContainsKey(Name ?? string.Empty); + public bool IsConnected + { + get { return ServerState.Instance.ConnectedFileSystems.ContainsKey(Name ?? string.Empty); } + } [ScriptIgnore] [JsonIgnore] [XmlIgnore] internal bool NeedSave { get; set; } = false; - - private static AuthorizationFactory AuthInstance = new AuthorizationFactory("AppGlue.dll"); + private static AuthorizationFactory _cache; //lazy init, because + private static AuthorizationFactory AuthInstance + { + get { return new AuthorizationFactory("AppGlue.dll"); } + } public IFileSystem Connect() { diff --git a/Shoko.Server/Models/SVR_GroupFilter.cs b/Shoko.Server/Models/SVR_GroupFilter.cs index bd7cde73c..095f974fa 100644 --- a/Shoko.Server/Models/SVR_GroupFilter.cs +++ b/Shoko.Server/Models/SVR_GroupFilter.cs @@ -4,11 +4,9 @@ using System.Linq; using FluentNHibernate.MappingModel; using Newtonsoft.Json; -using NHibernate; using NLog; using NutzCode.InMemoryIndex; using Shoko.Commons.Extensions; -using Shoko.Models; using Shoko.Models.Client; using Shoko.Models.Enums; using Shoko.Models.Server; @@ -16,15 +14,12 @@ using Shoko.Server.Databases; using Shoko.Server.Extensions; using Shoko.Server.Repositories; +using Shoko.Server.Repositories.NHibernate; namespace Shoko.Server.Models { public class SVR_GroupFilter : GroupFilter { - public SVR_GroupFilter() - { - } - public int GroupsIdsVersion { get; set; } public string GroupsIdsString { get; set; } @@ -61,7 +56,7 @@ public virtual Dictionary> GroupsIds if (_groupsId.Count == 0 && GroupsIdsVersion == GROUPFILTER_VERSION) { Dictionary> vals = - Newtonsoft.Json.JsonConvert.DeserializeObject>>(GroupsIdsString); + JsonConvert.DeserializeObject>>(GroupsIdsString); if (vals != null) _groupsId = vals.ToDictionary(a => a.Key, a => new HashSet(a.Value)); } @@ -77,7 +72,7 @@ public virtual Dictionary> SeriesIds if (_seriesId.Count == 0 && SeriesIdsVersion == SERIEFILTER_VERSION) { Dictionary> vals = - Newtonsoft.Json.JsonConvert.DeserializeObject>>(SeriesIdsString); + JsonConvert.DeserializeObject>>(SeriesIdsString); if (vals != null) _seriesId = vals.ToDictionary(a => a.Key, a => new HashSet(a.Value)); } @@ -93,7 +88,7 @@ public virtual List Conditions if (_conditions.Count == 0 && !string.IsNullOrEmpty(GroupConditions)) { _conditions = - Newtonsoft.Json.JsonConvert.DeserializeObject>(GroupConditions); + JsonConvert.DeserializeObject>(GroupConditions); } return _conditions; } @@ -112,19 +107,6 @@ public override string ToString() return $"{GroupFilterID} - {GroupFilterName}"; } - /* - public List FilterConditions - { - get - { - if (VirtualContract != null) - return VirtualContract.FilterConditions.Select(a => new GroupFilterCondition { ConditionOperator = a.ConditionOperator, ConditionType = a.ConditionType,ConditionParameter = a.ConditionParameter,GroupFilterID = a.GroupFilterID ?? 0}).ToList(); - GroupFilterConditionRepository repConds = new GroupFilterConditionRepository(); - return repConds.GetByGroupFilterID(this.GroupFilterID); - } - } - */ - public List SortCriteriaList { get @@ -146,7 +128,7 @@ public List SortCriteriaList { GroupFilterSortingCriteria gfsc = new GroupFilterSortingCriteria { - GroupFilterID = this.GroupFilterID, + GroupFilterID = GroupFilterID, SortType = (GroupFilterSorting)stype, SortDirection = (GroupFilterSortDirection)sdir }; @@ -168,18 +150,18 @@ public CL_GroupFilter ToClient() } CL_GroupFilter contract = new CL_GroupFilter { - GroupFilterID = this.GroupFilterID, - GroupFilterName = this.GroupFilterName, - ApplyToSeries = this.ApplyToSeries, - BaseCondition = this.BaseCondition, - SortingCriteria = this.SortingCriteria, - Locked = this.Locked, - FilterType = this.FilterType, - ParentGroupFilterID = this.ParentGroupFilterID, - InvisibleInClients = this.InvisibleInClients, + GroupFilterID = GroupFilterID, + GroupFilterName = GroupFilterName, + ApplyToSeries = ApplyToSeries, + BaseCondition = BaseCondition, + SortingCriteria = SortingCriteria, + Locked = Locked, + FilterType = FilterType, + ParentGroupFilterID = ParentGroupFilterID, + InvisibleInClients = InvisibleInClients, FilterConditions = Conditions, - Groups = this.GroupsIds, - Series = this.SeriesIds, + Groups = GroupsIds, + Series = SeriesIds, Childs = GroupFilterID == 0 ? new HashSet() : RepoFactory.GroupFilter.GetByParentID(GroupFilterID).Select(a => a.GroupFilterID).ToHashSet() @@ -209,18 +191,10 @@ public static SVR_GroupFilter FromClient(CL_GroupFilter gfc) } public CL_GroupFilterExtended ToClientExtended(SVR_JMMUser user) - { - using (var session = DatabaseFactory.SessionFactory.OpenSession()) - { - return ToClientExtended(session, user); - } - } - - public CL_GroupFilterExtended ToClientExtended(ISession session, SVR_JMMUser user) { CL_GroupFilterExtended contract = new CL_GroupFilterExtended { - GroupFilter = this.ToClient(), + GroupFilter = ToClient(), GroupCount = 0, SeriesCount = 0 }; @@ -228,74 +202,9 @@ public CL_GroupFilterExtended ToClientExtended(ISession session, SVR_JMMUser use { contract.GroupCount = GroupsIds[user.JMMUserID].Count; } -/* - // find all the groups for thise group filter - AnimeGroupRepository repGroups = new AnimeGroupRepository(); - List allGrps = repGroups.GetAll(session); - - if ((StatsCache.Instance.StatUserGroupFilter.ContainsKey(user.JMMUserID)) && (StatsCache.Instance.StatUserGroupFilter[user.JMMUserID].ContainsKey(this.GroupFilterID))) - { - HashSet groups = StatsCache.Instance.StatUserGroupFilter[user.JMMUserID][GroupFilterID]; - foreach (AnimeGroup grp in allGrps) - { - if (groups.Contains(grp.AnimeGroupID)) - contract.GroupCount++; - } - }*/ return contract; } - /* - public void UpdateGroupFilterUser(JMMUser ruser) - { - AnimeGroupRepository repGroups = new AnimeGroupRepository(); - AnimeGroup_UserRepository repUserGroups = new AnimeGroup_UserRepository(); - JMMUserRepository repUser = new JMMUserRepository(); - GroupFilterRepository repGrpFilter = new GroupFilterRepository(); - List users = new List(); - if ((this.FilterType & (int) GroupFilterType.Directory) == (int) GroupFilterType.Directory) - return; - if (ruser != null) - users.Add(ruser); - else - users = repUser.GetAll(); - bool change = false; - foreach (JMMUser user in users) - { - List allGrps = repGroups.GetAllTopLevelGroups(); // No Need of subgroups - - foreach (AnimeGroup grp in allGrps) - { - if (EvaluateGroupFilter(grp.GetUserContract(user.JMMUserID),user.Contract)) - { - if (!GroupsIds.ContainsKey(user.JMMUserID)) - { - GroupsIds[user.JMMUserID] = new HashSet(); - } - if (!GroupsIds[user.JMMUserID].Contains(grp.AnimeGroupID)) - { - GroupsIds[user.JMMUserID].Add(grp.AnimeGroupID); - change = true; - } - } - else - { - if (GroupsIds.ContainsKey(user.JMMUserID)) - { - if (GroupsIds[user.JMMUserID].Contains(grp.AnimeGroupID)) - { - GroupsIds[user.JMMUserID].Remove(grp.AnimeGroupID); - change = true; - } - } - } - } - } - if (change) - repGrpFilter.Save(this, true, null); - } - */ - public bool CalculateGroupFilterSeries(CL_AnimeSeries_User ser, JMMUser user, int jmmUserId) { @@ -399,6 +308,7 @@ public void CalculateGroupsAndSeries() private void EvaluateAnimeGroups() { IReadOnlyList users = RepoFactory.JMMUser.GetAll(); + // make sure the user has not filtered this out foreach (SVR_AnimeGroup grp in RepoFactory.AnimeGroup.GetAllTopLevelGroups()) { foreach (SVR_JMMUser user in users) @@ -432,7 +342,7 @@ public static CL_GroupFilter EvaluateContract(CL_GroupFilter gfc) if (gf.ApplyToSeries == 1) { gf.EvaluateAnimeSeries(); - + foreach (int user in gf.SeriesIds.Keys) { gf.GroupsIds[user] = gf.SeriesIds[user].Select(a => RepoFactory.AnimeSeries.GetByID(a)? @@ -443,7 +353,7 @@ public static CL_GroupFilter EvaluateContract(CL_GroupFilter gfc) else { gf.EvaluateAnimeGroups(); - + foreach (int user in gf.GroupsIds.Keys) { gf.SeriesIds[user] = gf.GroupsIds[user].SelectMany(a => RepoFactory.AnimeGroup.GetByID(a)?.GetAllSeries()?.Select(b => b?.AnimeSeriesID ?? -1)) @@ -451,7 +361,7 @@ public static CL_GroupFilter EvaluateContract(CL_GroupFilter gfc) .ToHashSet(); } } - + return gf.ToClient(); } @@ -459,15 +369,13 @@ public static CL_GroupFilter EvaluateContract(CL_GroupFilter gfc) public bool EvaluateGroupFilter(CL_AnimeGroup_User contractGroup, JMMUser curUser) { //Directories don't count - if ((this.FilterType & (int) GroupFilterType.Directory) == (int) GroupFilterType.Directory) + if ((FilterType & (int) GroupFilterType.Directory) == (int) GroupFilterType.Directory) return false; - // sub groups don't count - if (contractGroup.AnimeGroupParentID.HasValue) return false; + if (curUser.GetHideCategories().FindInEnumerable(contractGroup.Stat_AllTags)) return false; - // make sure the user has not filtered this out - if ((curUser != null) && curUser.GetHideCategories().FindInEnumerable(contractGroup.Stat_AllTags)) - return false; + // sub groups don't count + if (contractGroup.AnimeGroupParentID.HasValue) return false; // first check for anime groups which are included exluded every time foreach (GroupFilterCondition gfc in Conditions) @@ -489,10 +397,10 @@ public bool EvaluateGroupFilter(CL_AnimeGroup_User contractGroup, JMMUser curUse bool exclude = BaseCondition == (int) GroupFilterBaseCondition.Exclude; - return exclude ^ EvaluateConditions(contractGroup, curUser); + return exclude ^ EvaluateConditions(contractGroup); } - private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUser) + private bool EvaluateConditions(CL_AnimeGroup_User contractGroup) { NumberStyles style = NumberStyles.Number; CultureInfo culture = CultureInfo.InvariantCulture; @@ -514,20 +422,19 @@ private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUse (contractGroup.MissingEpisodeCount > 0 || contractGroup.MissingEpisodeCountGroups > 0) == false) return false; if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Exclude && - (contractGroup.MissingEpisodeCount > 0 || contractGroup.MissingEpisodeCountGroups > 0) == - true) return false; + (contractGroup.MissingEpisodeCount > 0 || contractGroup.MissingEpisodeCountGroups > 0)) return false; break; case GroupFilterConditionType.MissingEpisodesCollecting: if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Include && contractGroup.MissingEpisodeCountGroups > 0 == false) return false; if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Exclude && - contractGroup.MissingEpisodeCountGroups > 0 == true) return false; + contractGroup.MissingEpisodeCountGroups > 0) return false; break; case GroupFilterConditionType.Tag: List tags = gfc.ConditionParameter.Trim() - .Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries) + .Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) .Select(a => a.ToLowerInvariant().Trim()) .Where(a => !string.IsNullOrWhiteSpace(a)) .ToList(); @@ -574,7 +481,7 @@ private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUse contractGroup.WatchedEpisodeCount > 0 == false) return false; if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Exclude && - contractGroup.WatchedEpisodeCount > 0 == true) + contractGroup.WatchedEpisodeCount > 0) return false; break; @@ -583,7 +490,7 @@ private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUse contractGroup.UnwatchedEpisodeCount > 0 == false) return false; if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Exclude && - contractGroup.UnwatchedEpisodeCount > 0 == true) + contractGroup.UnwatchedEpisodeCount > 0) return false; break; @@ -592,7 +499,7 @@ private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUse contractGroup.Stat_HasTvDBLink == false) return false; if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Exclude && - contractGroup.Stat_HasTvDBLink == true) + contractGroup.Stat_HasTvDBLink) return false; break; @@ -601,7 +508,7 @@ private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUse contractGroup.Stat_HasMALLink == false) return false; if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Exclude && - contractGroup.Stat_HasMALLink == true) + contractGroup.Stat_HasMALLink) return false; break; @@ -610,7 +517,7 @@ private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUse contractGroup.Stat_HasMovieDBLink == false) return false; if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Exclude && - contractGroup.Stat_HasMovieDBLink == true) + contractGroup.Stat_HasMovieDBLink) return false; break; @@ -628,7 +535,7 @@ private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUse contractGroup.Stat_IsComplete == false) return false; if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Exclude && - contractGroup.Stat_IsComplete == true) + contractGroup.Stat_IsComplete) return false; break; @@ -637,7 +544,7 @@ private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUse contractGroup.Stat_HasFinishedAiring == false) return false; if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Exclude && - contractGroup.Stat_HasFinishedAiring == true) + contractGroup.Stat_HasFinishedAiring) return false; break; @@ -645,14 +552,14 @@ private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUse if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Include && contractGroup.Stat_UserVotePermanent.HasValue == false) return false; if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Exclude && - contractGroup.Stat_UserVotePermanent.HasValue == true) return false; + contractGroup.Stat_UserVotePermanent.HasValue) return false; break; case GroupFilterConditionType.UserVotedAny: if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Include && contractGroup.Stat_UserVoteOverall.HasValue == false) return false; if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Exclude && - contractGroup.Stat_UserVoteOverall.HasValue == true) return false; + contractGroup.Stat_UserVoteOverall.HasValue) return false; break; case GroupFilterConditionType.AirDate: @@ -800,7 +707,7 @@ private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUse case GroupFilterConditionType.CustomTags: List ctags = gfc.ConditionParameter.Trim() - .Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries) + .Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) .Select(a => a.ToLowerInvariant().Trim()) .ToList(); bool foundTag = ctags.FindInEnumerable(contractGroup.Stat_AllCustomTags); @@ -811,7 +718,7 @@ private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUse case GroupFilterConditionType.AnimeType: List ctypes = gfc.ConditionParameter - .Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries) + .Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) .Select(a => ((int) Commons.Extensions.Models.RawToType(a)).ToString()) .ToList(); bool foundAnimeType = ctypes.FindInEnumerable(contractGroup.Stat_AnimeTypes); @@ -823,7 +730,7 @@ private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUse case GroupFilterConditionType.VideoQuality: List vqs = gfc.ConditionParameter - .Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries) + .Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) .Select(a => a.ToLowerInvariant().Trim()) .ToList(); bool foundVid = vqs.FindInEnumerable(contractGroup.Stat_AllVideoQuality); @@ -839,7 +746,7 @@ private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUse case GroupFilterConditionType.AudioLanguage: List als = gfc.ConditionParameter.Trim() - .Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries) + .Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) .Select(a => a.ToLowerInvariant().Trim()) .ToList(); bool foundLang = als.FindInEnumerable(contractGroup.Stat_AudioLanguages); @@ -850,7 +757,7 @@ private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUse case GroupFilterConditionType.SubtitleLanguage: List ass = gfc.ConditionParameter - .Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries) + .Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) .Select(a => a.ToLowerInvariant().Trim()) .ToList(); bool foundSub = ass.FindInEnumerable(contractGroup.Stat_SubtitleLanguages); @@ -866,13 +773,10 @@ private bool EvaluateConditions(CL_AnimeGroup_User contractGroup, JMMUser curUse public bool EvaluateGroupFilter(CL_AnimeSeries_User contractSerie, JMMUser curUser) { //Directories don't count - if ((this.FilterType & (int) GroupFilterType.Directory) == (int) GroupFilterType.Directory) + if ((FilterType & (int) GroupFilterType.Directory) == (int) GroupFilterType.Directory) return false; - - // make sure the user has not filtered this out - if ((curUser != null) && - curUser.GetHideCategories().FindInEnumerable(contractSerie.AniDBAnime.AniDBAnime.GetAllTags())) + if (curUser.GetHideCategories().FindInEnumerable(contractSerie.AniDBAnime.AniDBAnime.GetAllTags())) return false; bool exclude = BaseCondition == (int) GroupFilterBaseCondition.Exclude; @@ -895,20 +799,19 @@ private bool EvaluateConditions(CL_AnimeSeries_User contractSerie, JMMUser curUs (contractSerie.MissingEpisodeCount > 0 || contractSerie.MissingEpisodeCountGroups > 0) == false) return false; if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Exclude && - (contractSerie.MissingEpisodeCount > 0 || contractSerie.MissingEpisodeCountGroups > 0) == - true) return false; + (contractSerie.MissingEpisodeCount > 0 || contractSerie.MissingEpisodeCountGroups > 0)) return false; break; case GroupFilterConditionType.MissingEpisodesCollecting: if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Include && contractSerie.MissingEpisodeCountGroups > 0 == false) return false; if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Exclude && - contractSerie.MissingEpisodeCountGroups > 0 == true) return false; + contractSerie.MissingEpisodeCountGroups > 0) return false; break; case GroupFilterConditionType.Tag: List tags = gfc.ConditionParameter.Trim() - .Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries) + .Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) .Select(a => a.ToLowerInvariant().Trim()) .Where(a => !string.IsNullOrWhiteSpace(a)) .ToList(); @@ -966,7 +869,7 @@ private bool EvaluateConditions(CL_AnimeSeries_User contractSerie, JMMUser curUs contractSerie.WatchedEpisodeCount > 0 == false) return false; if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Exclude && - contractSerie.WatchedEpisodeCount > 0 == true) + contractSerie.WatchedEpisodeCount > 0) return false; break; @@ -975,7 +878,7 @@ private bool EvaluateConditions(CL_AnimeSeries_User contractSerie, JMMUser curUs contractSerie.UnwatchedEpisodeCount > 0 == false) return false; if (gfc.GetConditionOperatorEnum() == GroupFilterOperator.Exclude && - contractSerie.UnwatchedEpisodeCount > 0 == true) + contractSerie.UnwatchedEpisodeCount > 0) return false; break; @@ -1191,7 +1094,7 @@ private bool EvaluateConditions(CL_AnimeSeries_User contractSerie, JMMUser curUs case GroupFilterConditionType.CustomTags: List ctags = gfc.ConditionParameter.Trim() - .Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries) + .Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) .Select(a => a.ToLowerInvariant().Trim()) .ToList(); bool foundTag = @@ -1203,7 +1106,7 @@ private bool EvaluateConditions(CL_AnimeSeries_User contractSerie, JMMUser curUs case GroupFilterConditionType.AnimeType: List ctypes = gfc.ConditionParameter.Trim() - .Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries) + .Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) .Select( a => ((int) Commons.Extensions.Models.RawToType(a.ToLowerInvariant())).ToString()) .ToList(); @@ -1216,7 +1119,7 @@ private bool EvaluateConditions(CL_AnimeSeries_User contractSerie, JMMUser curUs case GroupFilterConditionType.VideoQuality: List vqs = gfc.ConditionParameter.Trim() - .Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries) + .Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) .Select(a => a.ToLowerInvariant().Trim()) .ToList(); bool foundVid = vqs.FindInEnumerable(contractSerie.AniDBAnime.Stat_AllVideoQuality); @@ -1233,7 +1136,7 @@ private bool EvaluateConditions(CL_AnimeSeries_User contractSerie, JMMUser curUs case GroupFilterConditionType.AudioLanguage: List als = gfc.ConditionParameter.Trim() - .Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries) + .Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) .Select(a => a.ToLowerInvariant().Trim()) .ToList(); bool foundLang = als.FindInEnumerable(contractSerie.AniDBAnime.Stat_AudioLanguages); @@ -1244,7 +1147,7 @@ private bool EvaluateConditions(CL_AnimeSeries_User contractSerie, JMMUser curUs case GroupFilterConditionType.SubtitleLanguage: List ass = gfc.ConditionParameter.Trim() - .Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries) + .Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) .Select(a => a.ToLowerInvariant().Trim()) .ToList(); bool foundSub = ass.FindInEnumerable(contractSerie.AniDBAnime.Stat_AudioLanguages); @@ -1296,7 +1199,7 @@ public void UpdateEntityReferenceStrings(bool updateGroups = true, bool updateSe public void QueueUpdate() { CommandRequest_RefreshGroupFilter cmdRefreshGroupFilter = - new CommandRequest_RefreshGroupFilter(this.GroupFilterID); + new CommandRequest_RefreshGroupFilter(GroupFilterID); cmdRefreshGroupFilter.Save(); } @@ -1304,17 +1207,17 @@ public override bool Equals(object obj) { if (!(obj is SVR_GroupFilter)) return false; SVR_GroupFilter other = obj as SVR_GroupFilter; - if (other.ApplyToSeries != this.ApplyToSeries) return false; - if (other.BaseCondition != this.BaseCondition) return false; - if (other.FilterType != this.FilterType) return false; - if (other.InvisibleInClients != this.InvisibleInClients) return false; - if (other.Locked != this.Locked) return false; - if (other.ParentGroupFilterID != this.ParentGroupFilterID) return false; - if (other.GroupFilterName != this.GroupFilterName) return false; - if (other.SortingCriteria != this.SortingCriteria) return false; - if (this.Conditions == null || this.Conditions.Count == 0) + if (other.ApplyToSeries != ApplyToSeries) return false; + if (other.BaseCondition != BaseCondition) return false; + if (other.FilterType != FilterType) return false; + if (other.InvisibleInClients != InvisibleInClients) return false; + if (other.Locked != Locked) return false; + if (other.ParentGroupFilterID != ParentGroupFilterID) return false; + if (other.GroupFilterName != GroupFilterName) return false; + if (other.SortingCriteria != SortingCriteria) return false; + if (Conditions == null || Conditions.Count == 0) { - this.Conditions = RepoFactory.GroupFilterCondition.GetByGroupFilterID(this.GroupFilterID); + Conditions = RepoFactory.GroupFilterCondition.GetByGroupFilterID(GroupFilterID); RepoFactory.GroupFilter.Save(this); } if (other.Conditions == null || other.Conditions.Count == 0) @@ -1322,9 +1225,9 @@ public override bool Equals(object obj) other.Conditions = RepoFactory.GroupFilterCondition.GetByGroupFilterID(other.GroupFilterID); RepoFactory.GroupFilter.Save(other); } - if (this.Conditions != null && other.Conditions != null) + if (Conditions != null && other.Conditions != null) { - if (!this.Conditions.ContentEquals(other.Conditions)) return false; + if (!Conditions.ContentEquals(other.Conditions)) return false; } return true; diff --git a/Shoko.Server/Models/SVR_ImportFolder.cs b/Shoko.Server/Models/SVR_ImportFolder.cs old mode 100644 new mode 100755 index d3b663091..0e4101dea --- a/Shoko.Server/Models/SVR_ImportFolder.cs +++ b/Shoko.Server/Models/SVR_ImportFolder.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Linq.Expressions; using System.Web.Script.Serialization; - using System.Xml.Serialization; using Newtonsoft.Json; using NLog; diff --git a/Shoko.Server/Models/SVR_VideoLocal.cs b/Shoko.Server/Models/SVR_VideoLocal.cs index 8d44d05e1..720748434 100644 --- a/Shoko.Server/Models/SVR_VideoLocal.cs +++ b/Shoko.Server/Models/SVR_VideoLocal.cs @@ -4,25 +4,22 @@ using System.Text; using System.Text.RegularExpressions; using System.Web.Script.Serialization; -using AniDBAPI; using FluentNHibernate.Utils; +using NLog; +using NutzCode.CloudFileSystem; +using Pri.LongPath; using Shoko.Commons.Utils; -using Shoko.Models; using Shoko.Models.Client; using Shoko.Models.Interfaces; using Shoko.Models.PlexAndKodi; using Shoko.Models.Server; using Shoko.Server.Commands; using Shoko.Server.Commands.MAL; -using NLog; using Shoko.Server.Extensions; -using NutzCode.CloudFileSystem; using Shoko.Server.LZ4; using Shoko.Server.PlexAndKodi; using Shoko.Server.Repositories; using Shoko.Server.Repositories.Cached; -using Pri.LongPath; -using Stream = Shoko.Models.PlexAndKodi.Stream; namespace Shoko.Server.Models { @@ -30,10 +27,6 @@ public class SVR_VideoLocal : VideoLocal, IHash { private static Logger logger = LogManager.GetCurrentClassLogger(); - public SVR_VideoLocal() - { - } - #region DB columns public int MediaVersion { get; set; } @@ -50,7 +43,7 @@ public SVR_VideoLocal() public const int MEDIA_VERSION = 3; - internal Media _media = null; + internal Media _media; public virtual Media Media { @@ -82,7 +75,7 @@ public string ToStringDetailed() { StringBuilder sb = new StringBuilder(""); sb.Append(Environment.NewLine); - sb.Append("VideoLocalID: " + VideoLocalID.ToString()); + sb.Append("VideoLocalID: " + VideoLocalID); sb.Append(Environment.NewLine); sb.Append("FileName: " + FileName); @@ -91,7 +84,7 @@ public string ToStringDetailed() sb.Append(Environment.NewLine); sb.Append("Hash: " + Hash); sb.Append(Environment.NewLine); - sb.Append("FileSize: " + FileSize.ToString()); + sb.Append("FileSize: " + FileSize); sb.Append(Environment.NewLine); /* try @@ -165,7 +158,7 @@ private void SaveWatchedStatus(bool watched, int userID, DateTime? watchedDate, vidUserRecord = new VideoLocal_User(); vidUserRecord.WatchedDate = DateTime.Now; vidUserRecord.JMMUserID = userID; - vidUserRecord.VideoLocalID = this.VideoLocalID; + vidUserRecord.VideoLocalID = VideoLocalID; if (watchedDate.HasValue) { @@ -278,7 +271,7 @@ public void ToggleWatchedStatus(bool watched, bool updateOnline, DateTime? watch // now lets find all the associated AniDB_File record if there is one if (user.IsAniDBUser == 1) { - SVR_AniDB_File aniFile = RepoFactory.AniDB_File.GetByHash(this.Hash); + SVR_AniDB_File aniFile = RepoFactory.AniDB_File.GetByHash(Hash); if (aniFile != null) { aniFile.IsWatched = mywatched; @@ -303,7 +296,7 @@ public void ToggleWatchedStatus(bool watched, bool updateOnline, DateTime? watch (!watched && ServerSettings.AniDB_MyList_SetUnwatched)) { CommandRequest_UpdateMyListFileStatus cmd = new CommandRequest_UpdateMyListFileStatus( - this.Hash, watched, false, + Hash, watched, false, watchedDate.HasValue ? AniDB.GetAniDBDateAsSeconds(watchedDate) : 0); cmd.Save(); } @@ -317,7 +310,7 @@ public void ToggleWatchedStatus(bool watched, bool updateOnline, DateTime? watch SVR_AnimeSeries ser = null; // get all files associated with this episode - List xrefs = RepoFactory.CrossRef_File_Episode.GetByHash(this.Hash); + List xrefs = RepoFactory.CrossRef_File_Episode.GetByHash(Hash); Dictionary toUpdateSeries = new Dictionary(); if (watched) { @@ -457,21 +450,21 @@ public CL_VideoLocal ToClient(int userID) { CL_VideoLocal cl = new CL_VideoLocal { - CRC32 = this.CRC32, - DateTimeUpdated = this.DateTimeUpdated, - FileName = this.FileName, - FileSize = this.FileSize, - Hash = this.Hash, - HashSource = this.HashSource, - IsIgnored = this.IsIgnored, - IsVariation = this.IsVariation, - Duration = this.Duration, - MD5 = this.MD5, - SHA1 = this.SHA1, - VideoLocalID = this.VideoLocalID, + CRC32 = CRC32, + DateTimeUpdated = DateTimeUpdated, + FileName = FileName, + FileSize = FileSize, + Hash = Hash, + HashSource = HashSource, + IsIgnored = IsIgnored, + IsVariation = IsVariation, + Duration = Duration, + MD5 = MD5, + SHA1 = SHA1, + VideoLocalID = VideoLocalID, Places = Places.Select(a => a.ToClient()).ToList() }; - VideoLocal_User userRecord = this.GetUserRecord(userID); + VideoLocal_User userRecord = GetUserRecord(userID); if (userRecord?.WatchedDate == null) { cl.IsWatched = 0; @@ -563,7 +556,7 @@ public CL_VideoDetailed ToClientDetailed(int userID) CL_VideoDetailed cl = new CL_VideoDetailed(); // get the cross ref episode - List xrefs = this.EpisodeCrossRefs; + List xrefs = EpisodeCrossRefs; if (xrefs.Count == 0) return null; cl.Percentage = xrefs[0].Percentage; @@ -571,20 +564,20 @@ public CL_VideoDetailed ToClientDetailed(int userID) cl.CrossRefSource = xrefs[0].CrossRefSource; cl.AnimeEpisodeID = xrefs[0].EpisodeID; - cl.VideoLocal_FileName = this.FileName; - cl.VideoLocal_Hash = this.Hash; - cl.VideoLocal_FileSize = this.FileSize; - cl.VideoLocalID = this.VideoLocalID; - cl.VideoLocal_IsIgnored = this.IsIgnored; - cl.VideoLocal_IsVariation = this.IsVariation; + cl.VideoLocal_FileName = FileName; + cl.VideoLocal_Hash = Hash; + cl.VideoLocal_FileSize = FileSize; + cl.VideoLocalID = VideoLocalID; + cl.VideoLocal_IsIgnored = IsIgnored; + cl.VideoLocal_IsVariation = IsVariation; cl.Places = Places.Select(a => a.ToClient()).ToList(); - cl.VideoLocal_MD5 = this.MD5; - cl.VideoLocal_SHA1 = this.SHA1; - cl.VideoLocal_CRC32 = this.CRC32; - cl.VideoLocal_HashSource = this.HashSource; + cl.VideoLocal_MD5 = MD5; + cl.VideoLocal_SHA1 = SHA1; + cl.VideoLocal_CRC32 = CRC32; + cl.VideoLocal_HashSource = HashSource; - VideoLocal_User userRecord = this.GetUserRecord(userID); + VideoLocal_User userRecord = GetUserRecord(userID); if (userRecord?.WatchedDate == null) { cl.VideoLocal_IsWatched = 0; @@ -608,7 +601,7 @@ public CL_VideoDetailed ToClientDetailed(int userID) cl.VideoInfo_VideoResolution = VideoResolution; // AniDB File - SVR_AniDB_File anifile = this.GetAniDBFile(); // to prevent multiple db calls + SVR_AniDB_File anifile = GetAniDBFile(); // to prevent multiple db calls if (anifile != null) { cl.AniDB_Anime_GroupName = anifile.Anime_GroupName; @@ -660,7 +653,7 @@ public CL_VideoDetailed ToClientDetailed(int userID) } - AniDB_ReleaseGroup relGroup = this.ReleaseGroup; // to prevent multiple db calls + AniDB_ReleaseGroup relGroup = ReleaseGroup; // to prevent multiple db calls if (relGroup != null) cl.ReleaseGroup = relGroup; else @@ -673,20 +666,20 @@ public CL_VideoLocal_ManualLink ToContractManualLink(int userID) { CL_VideoLocal_ManualLink cl = new CL_VideoLocal_ManualLink { - CRC32 = this.CRC32, - DateTimeUpdated = this.DateTimeUpdated, - FileName = this.FileName, - FileSize = this.FileSize, - Hash = this.Hash, - HashSource = this.HashSource, - IsIgnored = this.IsIgnored, - IsVariation = this.IsVariation, - MD5 = this.MD5, - SHA1 = this.SHA1, - VideoLocalID = this.VideoLocalID, + CRC32 = CRC32, + DateTimeUpdated = DateTimeUpdated, + FileName = FileName, + FileSize = FileSize, + Hash = Hash, + HashSource = HashSource, + IsIgnored = IsIgnored, + IsVariation = IsVariation, + MD5 = MD5, + SHA1 = SHA1, + VideoLocalID = VideoLocalID, Places = Places.Select(a => a.ToClient()).ToList() }; - VideoLocal_User userRecord = this.GetUserRecord(userID); + VideoLocal_User userRecord = GetUserRecord(userID); if (userRecord?.WatchedDate == null) { cl.IsWatched = 0; diff --git a/Shoko.Server/Models/SVR_VideoLocal_Place.cs b/Shoko.Server/Models/SVR_VideoLocal_Place.cs index b7006637f..fdb810ad6 100644 --- a/Shoko.Server/Models/SVR_VideoLocal_Place.cs +++ b/Shoko.Server/Models/SVR_VideoLocal_Place.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -9,7 +10,6 @@ using NLog; using NutzCode.CloudFileSystem; using NutzCode.CloudFileSystem.Plugins.LocalFileSystem; -using Pri.LongPath; using Shoko.Models.Azure; using Shoko.Models.PlexAndKodi; using Shoko.Models.Server; @@ -22,6 +22,8 @@ using Shoko.Server.Providers.Azure; using Shoko.Server.Repositories; using Shoko.Server.Repositories.Cached; +using Path = Pri.LongPath.Path; +using Stream = Shoko.Models.PlexAndKodi.Stream; namespace Shoko.Server.Models { @@ -53,6 +55,21 @@ public string FullServerPath // returns false if we should try again after the timer private bool RenameFile() { + if (ImportFolder == null) + { + logger.Error( + $"Error: The renamer can't get the import folder for ImportFolderID: {ImportFolderID}, File: {FilePath}"); + return false; + } + + IFileSystem filesys = ImportFolder.FileSystem; + if (filesys == null) + { + logger.Error( + $"Error: The renamer can't get the filesystem for: {FullServerPath}"); + return true; + } + var renamer = RenameFileHelper.GetRenamer(); if (renamer == null) return true; string renamed = renamer.GetFileName(this); @@ -68,20 +85,6 @@ private bool RenameFile() return true; } - if (ImportFolder == null) - { - logger.Error( - $"Error: The renamer can't get the import folder for ImportFolderID: {ImportFolderID}, File: {FilePath}"); - return false; - } - - IFileSystem filesys = ImportFolder.FileSystem; - if (filesys == null) - { - logger.Error( - $"Error: The renamer can't get the filesystem for: {FullServerPath}"); - return true; - } // actually rename the file string fullFileName = FullServerPath; @@ -277,7 +280,7 @@ public void RemoveRecordWithOpenTransaction(ISession session, ICollection fobj = fs?.Resolve(FullServerPath); if (fobj == null || !fobj.IsOk || fobj.Result is IDirectory) return null; @@ -337,8 +340,13 @@ public bool RefreshMediaInfo() } if (m == null && FullServerPath != null) { - string name = (ImportFolder?.CloudID == null) - ? FullServerPath.Replace("/", "\\") + if (GetFile() == null) + { + logger.Error($"File {FullServerPath ?? VideoLocal_Place_ID.ToString()} failed to read MediaInfo"); + return false; + } + string name = (ImportFolder.CloudID == null) + ? FullServerPath.Replace("/", $"{Path.DirectorySeparatorChar}") : ((IProvider) null).ReplaceSchemeHost(((IProvider) null).ConstructVideoLocalStream(0, VideoLocalID.ToString(), "file", false)); m = MediaConvert.Convert(name, GetFile()); //Mediainfo should have libcurl.dll for http @@ -386,7 +394,7 @@ public bool RefreshMediaInfo() info.Media = m; return true; } - logger.Error($"File {FullServerPath ?? VideoLocal_Place_ID.ToString()} does not exist, unable to read media information from it"); + logger.Error($"File {FullServerPath ?? VideoLocal_Place_ID.ToString()} failed to read MediaInfo"); } catch (Exception e) { @@ -404,34 +412,46 @@ public bool RemoveAndDeleteFile() IFileSystem fileSystem = ImportFolder?.FileSystem; if (fileSystem == null) { - logger.Error("Unable to delete file, filesystem not found. Removing record."); + logger.Info("Unable to delete file, filesystem not found. Removing record."); RemoveRecord(); return true; } if (FullServerPath == null) { - logger.Error("Unable to delete file, fullserverpath is null. Removing record."); + logger.Info("Unable to delete file, fullserverpath is null. Removing record."); RemoveRecord(); return true; } FileSystemResult fr = fileSystem.Resolve(FullServerPath); if (fr == null || !fr.IsOk) { - logger.Error($"Unable to find file. Removing Record: {FullServerPath}"); + logger.Info($"Unable to find file. Removing Record: {FullServerPath}"); RemoveRecord(); return true; } - IFile file = fr.Result as IFile; - if (file == null) + if (!(fr.Result is IFile file)) { - logger.Error($"Seems '{FullServerPath}' is a directory."); + logger.Info($"Seems '{FullServerPath}' is a directory. Removing Record"); RemoveRecord(); return true; } - FileSystemResult fs = file.Delete(false); - if (fs == null || !fs.IsOk) + try{ + FileSystemResult fs = file.Delete(false); + if (fs == null || !fs.IsOk) + { + logger.Error($"Unable to delete file '{FullServerPath}': {fs?.Error ?? "No Error Message"}"); + return false; + } + } + catch (Exception ex) { - logger.Error($"Unable to delete file '{FullServerPath}'"); + if (ex is FileNotFoundException) + { + RemoveRecord(); + return true; + } + + logger.Error($"Unable to delete file '{FullServerPath}': {ex}"); return false; } RemoveRecord(); @@ -451,37 +471,49 @@ public string RemoveAndDeleteFileWithMessage() { logger.Info("Deleting video local place record and file: {0}", (FullServerPath ?? VideoLocal_Place_ID.ToString())); - IFileSystem fileSystem = ImportFolder.FileSystem; + IFileSystem fileSystem = ImportFolder?.FileSystem; if (fileSystem == null) { - logger.Error("Unable to delete file, filesystem not found. Removing record."); + logger.Info("Unable to delete file, filesystem not found. Removing record."); RemoveRecord(); - return "Unable to delete file, filesystem not found. Removing record."; + return string.Empty; } if (FullServerPath == null) { - logger.Error("Unable to delete file, fullserverpath is null. Removing record."); + logger.Info("Unable to delete file, fullserverpath is null. Removing record."); RemoveRecord(); - return "Unable to delete file, fullserverpath is null. Removing record."; + return string.Empty; } FileSystemResult fr = fileSystem.Resolve(FullServerPath); if (fr == null || !fr.IsOk) { - logger.Error($"Unable to find file. Removing Record: {FullServerPath}"); + logger.Info($"Unable to find file. Removing Record: {FullServerPath}"); RemoveRecord(); - return $"Unable to find file. Removing Record: {FullServerPath}"; + return string.Empty; } - IFile file = fr.Result as IFile; - if (file == null) + if (!(fr.Result is IFile file)) { - logger.Error($"Seems '{FullServerPath}' is a directory."); + logger.Info($"Seems '{FullServerPath}' is a directory. Removing Record"); RemoveRecord(); - return $"Seems '{FullServerPath}' is a directory."; + return string.Empty; + } + try{ + FileSystemResult fs = file.Delete(false); + if (fs == null || !fs.IsOk) + { + logger.Error($"Unable to delete file '{FullServerPath}': {fs?.Error ?? "No Error Message"}"); + return $"Unable to delete file '{FullServerPath}'"; + } } - FileSystemResult fs = file.Delete(false); - if (fs == null || !fs.IsOk) + catch (Exception ex) { - logger.Error($"Unable to delete file '{FullServerPath}'"); + if (ex is FileNotFoundException) + { + RemoveRecord(); + return string.Empty; + } + + logger.Error($"Unable to delete file '{FullServerPath}': {ex}"); return $"Unable to delete file '{FullServerPath}'"; } RemoveRecord(); @@ -504,34 +536,47 @@ public void RemoveAndDeleteFileWithOpenTransaction(ISession session, HashSet fr = fileSystem.Resolve(FullServerPath); if (fr == null || !fr.IsOk) { - logger.Error($"Unable to find file. Removing Record: {FullServerPath}"); + logger.Info($"Unable to find file. Removing Record: {FullServerPath}"); RemoveRecordWithOpenTransaction(session, episodesToUpdate, seriesToUpdate); return; } - IFile file = fr.Result as IFile; - if (file == null) + if (!(fr.Result is IFile file)) { - logger.Error($"Seems '{FullServerPath}' is a directory."); + logger.Info($"Seems '{FullServerPath}' is a directory. Removing Record"); RemoveRecordWithOpenTransaction(session, episodesToUpdate, seriesToUpdate); return; } - FileSystemResult fs = file.Delete(false); - if (fs == null || !fs.IsOk) + try + { + FileSystemResult fs = file.Delete(false); + if (fs == null || !fs.IsOk) + { + logger.Error($"Unable to delete file '{FullServerPath}': {fs?.Error ?? "No Error Message"}"); + return; + } + } + catch (Exception ex) { - logger.Error($"Unable to delete file '{FullServerPath}'"); + if (ex is FileNotFoundException) + { + RemoveRecordWithOpenTransaction(session, episodesToUpdate, seriesToUpdate); + return; + } + + logger.Error($"Unable to delete file '{FullServerPath}': {ex}"); return; } RemoveRecordWithOpenTransaction(session, episodesToUpdate, seriesToUpdate); @@ -567,15 +612,24 @@ public void RenameAndMoveAsRequired() } } succeeded = MoveFileIfRequired(); - if (succeeded) return; + if (!succeeded) + { Thread.Sleep((int)DELAY_IN_USE.FIRST); succeeded = MoveFileIfRequired(); - if (succeeded) return; + if (!succeeded) + { Thread.Sleep((int) DELAY_IN_USE.SECOND); succeeded = MoveFileIfRequired(); - if (succeeded) return; + if (!succeeded) + { Thread.Sleep((int) DELAY_IN_USE.THIRD); MoveFileIfRequired(); + if (!succeeded) return; //Same as above, but linux permissiosn. + } + } + } + + Utilities.LinuxFS.SetLinuxPermissions(this.FullServerPath, ServerSettings.Linux_UID, ServerSettings.Linux_GID, ServerSettings.Linux_Permission); } // returns false if we should retry @@ -594,6 +648,12 @@ private bool RenameIfRequired() public string MoveWithResultString(FileSystemResult fileSystemResult, string scriptName, bool force = false) { + if (FullServerPath == null) + { + logger.Error("Could not find or access the file to move: {0}", + VideoLocal_Place_ID); + return "ERROR: Unable to access file"; + } // check if this file is in the drop folder // otherwise we don't need to move it if (ImportFolder.IsDropSource == 0 && !force) @@ -677,7 +737,7 @@ public string MoveWithResultString(FileSystemResult fileSystemResult, s return "ERROR: The file is already at its desired location"; } - IFileSystem f = ImportFolder.FileSystem; + IFileSystem f = dropFolder.FileSystem; FileSystemResult dst = f.Resolve(newFullServerPath); if (dst != null && dst.IsOk) { @@ -699,6 +759,31 @@ public string MoveWithResultString(FileSystemResult fileSystemResult, s string originalFileName = FullServerPath; + // Handle Duplicate Files + var dups = RepoFactory.DuplicateFile.GetByFilePathAndImportFolder(FilePath, ImportFolderID).ToList(); + + foreach (var dup in dups) + { + // Move source + if (dup.FilePathFile1.Equals(FilePath) && dup.ImportFolderIDFile1 == ImportFolderID) + { + dup.FilePathFile1 = newFilePath; + dup.ImportFolderIDFile1 = destFolder.ImportFolderID; + } + else if (dup.FilePathFile2.Equals(FilePath) && dup.ImportFolderIDFile2 == ImportFolderID) + { + dup.FilePathFile2 = newFilePath; + dup.ImportFolderIDFile2 = destFolder.ImportFolderID; + } + // validate the dup file + // There are cases where a dup file was not cleaned up before, so we'll do it here, too + if (!dup.GetFullServerPath1() + .Equals(dup.GetFullServerPath2(), StringComparison.InvariantCultureIgnoreCase)) + RepoFactory.DuplicateFile.Save(dup); + else + RepoFactory.DuplicateFile.Delete(dup); + } + ImportFolderID = destFolder.ImportFolderID; FilePath = newFilePath; RepoFactory.VideoLocalPlace.Save(this); @@ -911,6 +996,31 @@ private bool MoveFileIfRequired() string originalFileName = FullServerPath; + // Handle Duplicate Files + var dups = RepoFactory.DuplicateFile.GetByFilePathAndImportFolder(FilePath, ImportFolderID).ToList(); + + foreach (var dup in dups) + { + // Move source + if (dup.FilePathFile1.Equals(FilePath) && dup.ImportFolderIDFile1 == ImportFolderID) + { + dup.FilePathFile1 = newFilePath; + dup.ImportFolderIDFile1 = destFolder.ImportFolderID; + } + else if (dup.FilePathFile2.Equals(FilePath) && dup.ImportFolderIDFile2 == ImportFolderID) + { + dup.FilePathFile2 = newFilePath; + dup.ImportFolderIDFile2 = destFolder.ImportFolderID; + } + // validate the dup file + // There are cases where a dup file was not cleaned up before, so we'll do it here, too + if (!dup.GetFullServerPath1() + .Equals(dup.GetFullServerPath2(), StringComparison.InvariantCultureIgnoreCase)) + RepoFactory.DuplicateFile.Save(dup); + else + RepoFactory.DuplicateFile.Delete(dup); + } + ImportFolderID = destFolder.ImportFolderID; FilePath = newFilePath; RepoFactory.VideoLocalPlace.Save(this); diff --git a/Shoko.Server/Properties/AssemblyInfo.cs b/Shoko.Server/Properties/AssemblyInfo.cs old mode 100644 new mode 100755 index 7c388cb67..47574d461 --- a/Shoko.Server/Properties/AssemblyInfo.cs +++ b/Shoko.Server/Properties/AssemblyInfo.cs @@ -5,6 +5,7 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. +[assembly: AssemblyTitle("Shoko Server")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -21,10 +22,6 @@ //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] - - - - // Version information for an assembly consists of the following four values: // // Major Version diff --git a/Shoko.Server/Providers/TvDB/TvDBApiHelper.cs b/Shoko.Server/Providers/TvDB/TvDBApiHelper.cs index b1674ca88..ebe08bcf7 100644 --- a/Shoko.Server/Providers/TvDB/TvDBApiHelper.cs +++ b/Shoko.Server/Providers/TvDB/TvDBApiHelper.cs @@ -1,26 +1,23 @@ -using Shoko.Models.Server; -using TvDbSharper; -using NLog; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Threading.Tasks; -using System.Web; -using Shoko.Server.Repositories; -using Shoko.Server.Extensions; -using Shoko.Models.TvDB; +using NLog; +using Pri.LongPath; +using Shoko.Commons.Extensions; using Shoko.Models.Enums; -using Shoko.Server.Models; +using Shoko.Models.Server; +using Shoko.Models.TvDB; using Shoko.Server.Commands; using Shoko.Server.Databases; -using Shoko.Server.Repositories.NHibernate; -using Pri.LongPath; - -using Shoko.Commons.Extensions; +using Shoko.Server.Extensions; +using Shoko.Server.Models; +using Shoko.Server.Repositories; +using TvDbSharper; using TvDbSharper.Dto; -using HttpUtility = Nancy.Helpers.HttpUtility; +using Language = TvDbSharper.Dto.Language; namespace Shoko.Server.Providers.TvDB { @@ -81,7 +78,7 @@ public static async Task GetSeriesInfoOnlineAsync(int seriesID) return tvSeries; } - catch (TvDbSharper.TvDbServerException exception) + catch (TvDbServerException exception) { if (exception.StatusCode == (int)HttpStatusCode.Unauthorized) { @@ -121,16 +118,17 @@ public static async Task> SearchSeriesAsync(st TvDBRateLimiter.Instance.EnsureRate(); var response = await client.Search.SearchSeriesByNameAsync(criteria); - TvDbSharper.Dto.SeriesSearchResult[] series = response.Data; + SeriesSearchResult[] series = response?.Data; + if (series == null) return results; - foreach (TvDbSharper.Dto.SeriesSearchResult item in series) + foreach (SeriesSearchResult item in series) { TVDB_Series_Search_Response searchResult = new TVDB_Series_Search_Response(); searchResult.Populate(item); results.Add(searchResult); } } - catch (TvDbSharper.TvDbServerException exception) + catch (TvDbServerException exception) { if (exception.StatusCode == (int)HttpStatusCode.Unauthorized) { @@ -287,14 +285,14 @@ public static async Task> GetLanguagesAsync() TvDBRateLimiter.Instance.EnsureRate(); var response = await client.Languages.GetAllAsync(); - TvDbSharper.Dto.Language[] apiLanguages = response.Data; + Language[] apiLanguages = response.Data; if (apiLanguages.Length <= 0) return languages; - foreach (TvDbSharper.Dto.Language item in apiLanguages) + foreach (Language item in apiLanguages) { - TvDB_Language lan = new TvDB_Language() + TvDB_Language lan = new TvDB_Language { Id = item.Id, EnglishName = item.EnglishName, @@ -304,7 +302,7 @@ public static async Task> GetLanguagesAsync() languages.Add(lan); } } - catch (TvDbSharper.TvDbServerException exception) + catch (TvDbServerException exception) { if (exception.StatusCode == (int)HttpStatusCode.Unauthorized) { @@ -328,18 +326,12 @@ public static async Task> GetLanguagesAsync() public static void DownloadAutomaticImages(int seriesID, bool forceDownload) { ImagesSummary summary = GetSeriesImagesCounts(seriesID); - if (summary.Fanart > 0) - { - DownloadAutomaticImages(GetFanartOnline(seriesID), seriesID, forceDownload); - } + if (summary == null) return; + if (summary.Fanart > 0) DownloadAutomaticImages(GetFanartOnline(seriesID), seriesID, forceDownload); if (summary.Poster > 0 || summary.Season > 0) - { DownloadAutomaticImages(GetPosterOnline(seriesID), seriesID, forceDownload); - } if (summary.Seasonwide > 0 || summary.Series > 0) - { DownloadAutomaticImages(GetBannerOnline(seriesID), seriesID, forceDownload); - } } static ImagesSummary GetSeriesImagesCounts(int seriesID) @@ -357,7 +349,7 @@ static async Task GetSeriesImagesCountsAsync(int seriesID) var response = await client.Series.GetImagesSummaryAsync(seriesID); return response.Data; } - catch (TvDbSharper.TvDbServerException exception) + catch (TvDbServerException exception) { if (exception.StatusCode == (int)HttpStatusCode.Unauthorized) { @@ -377,7 +369,7 @@ static async Task GetSeriesImagesAsync(int seriesID, KeyType type) { await CheckAuthorizationAsync(); - ImagesQuery query = new ImagesQuery() + ImagesQuery query = new ImagesQuery { KeyType = type }; @@ -387,7 +379,7 @@ static async Task GetSeriesImagesAsync(int seriesID, KeyType type) var response = await client.Series.GetImagesAsync(seriesID, query); return response.Data; } - catch (TvDbSharper.TvDbServerException exception) + catch (TvDbServerException exception) { if (exception.StatusCode == (int)HttpStatusCode.Unauthorized) { @@ -431,7 +423,7 @@ public static async Task> GetFanartOnlineAsync(int series if (img == null) { - img = new TvDB_ImageFanart() + img = new TvDB_ImageFanart { Enabled = 1 }; @@ -452,7 +444,7 @@ public static async Task> GetFanartOnlineAsync(int series RepoFactory.TvDB_ImageFanart.Delete(img.TvDB_ImageFanartID); } } - catch (TvDbSharper.TvDbServerException exception) + catch (TvDbServerException exception) { if (exception.StatusCode == (int)HttpStatusCode.Unauthorized) { @@ -498,7 +490,7 @@ public static async Task> GetPosterOnlineAsync(int series if (id == 0) { continue; } if (count >= ServerSettings.TvDB_AutoPostersAmount) break; - TvDB_ImagePoster img = RepoFactory.TvDB_ImagePoster.GetByTvDBID(id) ?? new TvDB_ImagePoster() + TvDB_ImagePoster img = RepoFactory.TvDB_ImagePoster.GetByTvDBID(id) ?? new TvDB_ImagePoster { Enabled = 1 }; @@ -518,7 +510,7 @@ public static async Task> GetPosterOnlineAsync(int series RepoFactory.TvDB_ImagePoster.Delete(img.TvDB_ImagePosterID); } } - catch (TvDbSharper.TvDbServerException exception) + catch (TvDbServerException exception) { if (exception.StatusCode == (int)HttpStatusCode.Unauthorized) { @@ -586,7 +578,7 @@ public static async Task> GetBannerOnlineAsync(int se RepoFactory.TvDB_ImageWideBanner.Delete(img.TvDB_ImageWideBannerID); } } - catch (TvDbSharper.TvDbServerException exception) + catch (TvDbServerException exception) { if (exception.StatusCode == (int)HttpStatusCode.Unauthorized) { @@ -727,7 +719,7 @@ static async Task> GetEpisodesOnlineAsync(int seriesID) apiEpisodes = firstResponse.Data.Concat(results.SelectMany(x => x.Data)).ToList(); } - catch (TvDbSharper.TvDbServerException exception) + catch (TvDbServerException exception) { if (exception.StatusCode == (int)HttpStatusCode.Unauthorized) { @@ -764,7 +756,7 @@ static async Task GetEpisodeDetailsAsync(int episodeID) var response = await client.Episodes.GetAsync(episodeID); return response.Data; } - catch (TvDbSharper.TvDbServerException exception) + catch (TvDbServerException exception) { if (exception.StatusCode == (int)HttpStatusCode.Unauthorized) { @@ -822,7 +814,7 @@ public static async Task QueueEpisodeImageDownloadAsync(BasicEpisode item, List< } } } - catch (TvDbSharper.TvDbServerException exception) + catch (TvDbServerException exception) { if (exception.StatusCode == (int)HttpStatusCode.Unauthorized) { @@ -998,7 +990,7 @@ public static async Task> GetUpdatedSeriesListAsync(string serverTime) return seriesList; } - catch (TvDbSharper.TvDbServerException exception) + catch (TvDbServerException exception) { if (exception.StatusCode == (int)HttpStatusCode.Unauthorized) { diff --git a/Shoko.Server/Repositories/BaseCachedRepository.cs b/Shoko.Server/Repositories/BaseCachedRepository.cs index ee228c775..734e46af7 100644 --- a/Shoko.Server/Repositories/BaseCachedRepository.cs +++ b/Shoko.Server/Repositories/BaseCachedRepository.cs @@ -13,6 +13,9 @@ public abstract class BaseCachedRepository : ICachedRepository, IRepositor { internal PocoCache Cache; + // Lock to allow updates from multiple threads. As a general rule, we lock the entire call to avoid stale state + protected readonly object globalDBLock = new object(); + public Action BeginDeleteCallback { get; set; } public Action DeleteWithOpenTransactionCallback { get; set; } public Action EndDeleteCallback { get; set; } @@ -27,6 +30,7 @@ public virtual void Populate(ISessionWrapper session, bool displayname = true) Commons.Properties.Resources.Database_Cache, typeof(T).Name.Replace("SVR_", string.Empty), string.Empty); + // This is only called from main thread, so we don't need to lock Cache = new PocoCache(session.CreateCriteria(typeof(T)).List(), SelectKey); PopulateIndexes(); } @@ -41,45 +45,69 @@ public virtual void Populate(bool displayname = true) protected abstract S SelectKey(T entity); - public virtual void ClearCache() + public void ClearCache() { - Cache.Clear(); + lock (globalDBLock) + { + Cache.Clear(); + } } // ReSharper disable once InconsistentNaming public virtual T GetByID(S id) { - return Cache.Get(id); + lock (globalDBLock) + { + return Cache.Get(id); + } } public T GetByID(ISession session, S id) { - return Cache.Get(id); + lock (globalDBLock) + { + return Cache.Get(id); + } } public T GetByID(ISessionWrapper session, S id) { - return Cache.Get(id); + lock (globalDBLock) + { + return Cache.Get(id); + } } public virtual IReadOnlyList GetAll() { - return Cache.Values.ToList(); + lock (globalDBLock) + { + return Cache.Values.ToList(); + } } - public virtual IReadOnlyList GetAll(int max_limit) + public IReadOnlyList GetAll(int max_limit) { - return Cache.Values.Take(max_limit).ToList(); + lock (globalDBLock) + { + return Cache.Values.Take(max_limit).ToList(); + } } public IReadOnlyList GetAll(ISession session) { - return Cache.Values.ToList(); + lock (globalDBLock) + { + return Cache.Values.ToList(); + } } public IReadOnlyList GetAll(ISessionWrapper session) { - return Cache.Values.ToList(); + lock (globalDBLock) + { + return Cache.Values.ToList(); + } } public virtual void Delete(S id) @@ -89,7 +117,8 @@ public virtual void Delete(S id) public virtual void Delete(T cr) { - if (cr != null) + if (cr == null) return; + lock (globalDBLock) { BeginDeleteCallback?.Invoke(cr); using (var session = DatabaseFactory.SessionFactory.OpenSession()) @@ -106,33 +135,32 @@ public virtual void Delete(T cr) } } - public virtual void Delete(IReadOnlyCollection objs) + public void Delete(IReadOnlyCollection objs) { if (objs.Count == 0) return; - foreach (T cr in objs) - BeginDeleteCallback?.Invoke(cr); - using (var session = DatabaseFactory.SessionFactory.OpenSession()) + lock (globalDBLock) { - using (var transaction = session.BeginTransaction()) + foreach (T cr in objs) BeginDeleteCallback?.Invoke(cr); + using (var session = DatabaseFactory.SessionFactory.OpenSession()) { - foreach (T cr in objs) + using (var transaction = session.BeginTransaction()) { - DeleteWithOpenTransactionCallback?.Invoke(session, cr); - Cache.Remove(cr); - session.Delete(cr); + foreach (T cr in objs) + { + DeleteWithOpenTransactionCallback?.Invoke(session, cr); + Cache.Remove(cr); + session.Delete(cr); + } + transaction.Commit(); } - transaction.Commit(); } - } - foreach (T cr in objs) - { - EndDeleteCallback?.Invoke(cr); + foreach (T cr in objs) EndDeleteCallback?.Invoke(cr); } } //This function do not run the BeginDeleteCallback and the EndDeleteCallback - public virtual void DeleteWithOpenTransaction(ISession session, S id) + public void DeleteWithOpenTransaction(ISession session, S id) { DeleteWithOpenTransaction(session, GetByID(id)); } @@ -140,7 +168,8 @@ public virtual void DeleteWithOpenTransaction(ISession session, S id) //This function do not run the BeginDeleteCallback and the EndDeleteCallback public virtual void DeleteWithOpenTransaction(ISession session, T cr) { - if (cr != null) + if (cr == null) return; + lock (globalDBLock) { DeleteWithOpenTransactionCallback?.Invoke(session, cr); Cache.Remove(cr); @@ -149,91 +178,120 @@ public virtual void DeleteWithOpenTransaction(ISession session, T cr) } //This function do not run the BeginDeleteCallback and the EndDeleteCallback - public virtual void DeleteWithOpenTransaction(ISession session, List objs) + public void DeleteWithOpenTransaction(ISession session, List objs) { if (objs.Count == 0) return; - foreach (T cr in objs) + lock (globalDBLock) { - DeleteWithOpenTransactionCallback?.Invoke(session, cr); - Cache.Remove(cr); - session.Delete(cr); + foreach (T cr in objs) + { + DeleteWithOpenTransactionCallback?.Invoke(session, cr); + Cache.Remove(cr); + session.Delete(cr); + } } } public virtual void Save(T obj) { - BeginSaveCallback?.Invoke(obj); - using (var session = DatabaseFactory.SessionFactory.OpenSession()) + lock (globalDBLock) { - using (var transaction = session.BeginTransaction()) + lock (obj) { - SaveWithOpenTransactionCallback?.Invoke(session.Wrap(), obj); - session.SaveOrUpdate(obj); - transaction.Commit(); + BeginSaveCallback?.Invoke(obj); + using (var session = DatabaseFactory.SessionFactory.OpenSession()) + { + using (var transaction = session.BeginTransaction()) + { + SaveWithOpenTransactionCallback?.Invoke(session.Wrap(), obj); + session.SaveOrUpdate(obj); + transaction.Commit(); + } + } + Cache.Update(obj); + EndSaveCallback?.Invoke(obj); } } - Cache.Update(obj); - EndSaveCallback?.Invoke(obj); } - public virtual void Save(IReadOnlyCollection objs) + public void Save(IReadOnlyCollection objs) { if (objs.Count == 0) return; - using (var session = DatabaseFactory.SessionFactory.OpenSession()) + lock (globalDBLock) { - using (var transaction = session.BeginTransaction()) + using (var session = DatabaseFactory.SessionFactory.OpenSession()) { - foreach (T obj in objs) + using (var transaction = session.BeginTransaction()) { - session.SaveOrUpdate(obj); - SaveWithOpenTransactionCallback?.Invoke(session.Wrap(), obj); + foreach (T obj in objs) + { + lock (obj) + { + session.SaveOrUpdate(obj); + SaveWithOpenTransactionCallback?.Invoke(session.Wrap(), obj); + } + } + transaction.Commit(); } - transaction.Commit(); } - } - foreach (T obj in objs) - { - Cache.Update(obj); - EndSaveCallback?.Invoke(obj); + foreach (T obj in objs) + { + Cache.Update(obj); + EndSaveCallback?.Invoke(obj); + } } } //This function do not run the BeginDeleteCallback and the EndDeleteCallback public virtual void SaveWithOpenTransaction(ISessionWrapper session, T obj) { - if (Equals(SelectKey(obj), default(S))) - { - session.Insert(obj); - } - else + lock (globalDBLock) { - session.Update(obj); - } + lock (obj) + { + if (Equals(SelectKey(obj), default(S))) + session.Insert(obj); + else + session.Update(obj); - SaveWithOpenTransactionCallback?.Invoke(session, obj); - Cache.Update(obj); + SaveWithOpenTransactionCallback?.Invoke(session, obj); + Cache.Update(obj); + } + } } //This function do not run the BeginDeleteCallback and the EndDeleteCallback public virtual void SaveWithOpenTransaction(ISession session, T obj) { - session.SaveOrUpdate(obj); - SaveWithOpenTransactionCallback?.Invoke(session.Wrap(), obj); - Cache.Update(obj); + lock (globalDBLock) + { + lock (obj) + { + session.SaveOrUpdate(obj); + SaveWithOpenTransactionCallback?.Invoke(session.Wrap(), obj); + Cache.Update(obj); + } + } } //This function do not run the BeginDeleteCallback and the EndDeleteCallback - public virtual void SaveWithOpenTransaction(ISession session, List objs) + public void SaveWithOpenTransaction(ISession session, List objs) { if (objs.Count == 0) return; - foreach (T obj in objs) + lock (globalDBLock) { - session.SaveOrUpdate(obj); - SaveWithOpenTransactionCallback?.Invoke(session.Wrap(), obj); - Cache.Update(obj); + foreach (T obj in objs) + { + lock (obj) + { + session.SaveOrUpdate(obj); + SaveWithOpenTransactionCallback?.Invoke(session.Wrap(), obj); + Cache.Update(obj); + } + } } } diff --git a/Shoko.Server/Repositories/BaseDirectRepository.cs b/Shoko.Server/Repositories/BaseDirectRepository.cs index 08116bdb7..c347dbdc2 100644 --- a/Shoko.Server/Repositories/BaseDirectRepository.cs +++ b/Shoko.Server/Repositories/BaseDirectRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using NHibernate; using Shoko.Server.Databases; using Shoko.Server.Repositories.NHibernate; @@ -10,6 +11,7 @@ namespace Shoko.Server.Repositories { public class BaseDirectRepository : IRepository where T : class { + protected readonly object globalDBLock = new object(); public Action BeginDeleteCallback { get; set; } public Action DeleteWithOpenTransactionCallback { get; set; } public Action EndDeleteCallback { get; set; } @@ -19,38 +21,56 @@ public class BaseDirectRepository : IRepository where T : class public virtual T GetByID(S id) { - using (var session = DatabaseFactory.SessionFactory.OpenSession()) + lock (globalDBLock) { - return session.Get(id); + using (var session = DatabaseFactory.SessionFactory.OpenSession()) + { + return session.Get(id); + } } } public virtual T GetByID(ISession session, S id) { - return session.Get(id); + lock (globalDBLock) + { + return session.Get(id); + } } public virtual T GetByID(ISessionWrapper session, S id) { - return session.Get(id); + lock (globalDBLock) + { + return session.Get(id); + } } public virtual IReadOnlyList GetAll() { - using (var session = DatabaseFactory.SessionFactory.OpenSession()) + lock (globalDBLock) { - return new List(session.CreateCriteria(typeof(T)).List()); + using (var session = DatabaseFactory.SessionFactory.OpenSession()) + { + return session.CreateCriteria(typeof(T)).List().ToList(); + } } } public virtual IReadOnlyList GetAll(ISession session) { - return new List(session.CreateCriteria(typeof(T)).List()); + lock (globalDBLock) + { + return session.CreateCriteria(typeof(T)).List().ToList(); + } } public virtual IReadOnlyList GetAll(ISessionWrapper session) { - return new List(session.CreateCriteria(typeof(T)).List()); + lock (globalDBLock) + { + return session.CreateCriteria(typeof(T)).List().ToList(); + } } @@ -61,7 +81,8 @@ public virtual void Delete(S id) public virtual void Delete(T cr) { - if (cr != null) + if (cr == null) return; + lock (globalDBLock) { BeginDeleteCallback?.Invoke(cr); using (var session = DatabaseFactory.SessionFactory.OpenSession()) @@ -77,26 +98,27 @@ public virtual void Delete(T cr) } } - public virtual void Delete(IReadOnlyCollection objs) + public void Delete(IReadOnlyCollection objs) { if (objs.Count == 0) return; - foreach (T obj in objs) - BeginDeleteCallback?.Invoke(obj); - using (var session = DatabaseFactory.SessionFactory.OpenSession()) + lock (globalDBLock) { - using (var transaction = session.BeginTransaction()) + foreach (T obj in objs) BeginDeleteCallback?.Invoke(obj); + using (var session = DatabaseFactory.SessionFactory.OpenSession()) { - foreach (T cr in objs) + using (var transaction = session.BeginTransaction()) { - DeleteWithOpenTransactionCallback?.Invoke(session, cr); - session.Delete(cr); + foreach (T cr in objs) + { + DeleteWithOpenTransactionCallback?.Invoke(session, cr); + session.Delete(cr); + } + transaction.Commit(); } - transaction.Commit(); } + foreach (T obj in objs) EndDeleteCallback?.Invoke(obj); } - foreach (T obj in objs) - EndDeleteCallback?.Invoke(obj); } //This function do not run the BeginDeleteCallback and the EndDeleteCallback @@ -108,77 +130,102 @@ public virtual void DeleteWithOpenTransaction(ISession session, S id) //This function do not run the BeginDeleteCallback and the EndDeleteCallback public virtual void DeleteWithOpenTransaction(ISession session, T cr) { - if (cr != null) + if (cr == null) return; + lock (globalDBLock) { session.Delete(cr); } } //This function do not run the BeginDeleteCallback and the EndDeleteCallback - public virtual void DeleteWithOpenTransaction(ISession session, List objs) + public void DeleteWithOpenTransaction(ISession session, List objs) { - if (objs.Count == 0) - return; - foreach (T cr in objs) + if (objs.Count == 0) return; + lock (globalDBLock) { - DeleteWithOpenTransactionCallback?.Invoke(session, cr); - session.Delete(cr); + foreach (T cr in objs) + { + lock (cr) + { + DeleteWithOpenTransactionCallback?.Invoke(session, cr); + session.Delete(cr); + } + } } } public virtual void Save(T obj) { - BeginSaveCallback?.Invoke(obj); - using (var session = DatabaseFactory.SessionFactory.OpenSession()) + lock (globalDBLock) { - using (var transaction = session.BeginTransaction()) + lock (obj) { - session.SaveOrUpdate(obj); - SaveWithOpenTransactionCallback?.Invoke(session.Wrap(), obj); - transaction.Commit(); + BeginSaveCallback?.Invoke(obj); + using (var session = DatabaseFactory.SessionFactory.OpenSession()) + { + using (var transaction = session.BeginTransaction()) + { + session.SaveOrUpdate(obj); + SaveWithOpenTransactionCallback?.Invoke(session.Wrap(), obj); + transaction.Commit(); + } + } + EndSaveCallback?.Invoke(obj); } } - EndSaveCallback?.Invoke(obj); } - public virtual void Save(IReadOnlyCollection objs) + public void Save(IReadOnlyCollection objs) { if (objs.Count == 0) return; - foreach (T obj in objs) - BeginSaveCallback?.Invoke(obj); - using (var session = DatabaseFactory.SessionFactory.OpenSession()) + lock (globalDBLock) { - using (var transaction = session.BeginTransaction()) + using (var session = DatabaseFactory.SessionFactory.OpenSession()) { - foreach (T obj in objs) + using (var transaction = session.BeginTransaction()) { - session.SaveOrUpdate(obj); - SaveWithOpenTransactionCallback?.Invoke(session.Wrap(), obj); + foreach (T obj in objs) + { + lock (obj) + { + BeginSaveCallback?.Invoke(obj); + session.SaveOrUpdate(obj); + SaveWithOpenTransactionCallback?.Invoke(session.Wrap(), obj); + EndSaveCallback?.Invoke(obj); + } + } + transaction.Commit(); } - transaction.Commit(); } } - foreach (T obj in objs) - EndSaveCallback?.Invoke(obj); } //This function do not run the BeginDeleteCallback and the EndDeleteCallback public virtual void SaveWithOpenTransaction(ISession session, T obj) { - session.SaveOrUpdate(obj); + lock (globalDBLock) + { + lock (obj) + { + session.SaveOrUpdate(obj); + } + } } //This function do not run the BeginDeleteCallback and the EndDeleteCallback - public virtual void SaveWithOpenTransaction(ISession session, List objs) + public void SaveWithOpenTransaction(ISession session, List objs) { if (objs.Count == 0) return; - foreach (T obj in objs) + lock (globalDBLock) { - session.SaveOrUpdate(obj); - SaveWithOpenTransactionCallback?.Invoke(session.Wrap(), obj); + foreach (T obj in objs) + { + session.SaveOrUpdate(obj); + SaveWithOpenTransactionCallback?.Invoke(session.Wrap(), obj); + } } } } diff --git a/Shoko.Server/Repositories/Cached/AniDB_AnimeRepository.cs b/Shoko.Server/Repositories/Cached/AniDB_AnimeRepository.cs index 1d56a48e1..2d7199bdf 100644 --- a/Shoko.Server/Repositories/Cached/AniDB_AnimeRepository.cs +++ b/Shoko.Server/Repositories/Cached/AniDB_AnimeRepository.cs @@ -63,11 +63,12 @@ public override void RegenerateDb() { foreach (SVR_AniDB_Anime anime in animeBatch) { - anime.Description = anime.Description.Replace('`', '\''); - anime.MainTitle = anime.MainTitle.Replace('`', '\''); - anime.AllTags = anime.AllTags.Replace('`', '\''); - anime.AllTitles = anime.AllTitles.Replace('`', '\''); + anime.Description = anime.Description?.Replace("`", "\'") ?? string.Empty; + anime.MainTitle = anime.MainTitle.Replace("`", "\'"); + anime.AllTags = anime.AllTags.Replace("`", "\'"); + anime.AllTitles = anime.AllTitles.Replace("`", "\'"); session.Update(anime); + Cache.Update(anime); count++; } @@ -85,75 +86,70 @@ public override void RegenerateDb() } } - - public override void Save(IReadOnlyCollection objs) - { - foreach (SVR_AniDB_Anime o in objs) - Save(o); - } - public override void Save(SVR_AniDB_Anime obj) { - lock (obj) + lock (globalDBLock) { - if (obj.AniDB_AnimeID == 0) + lock (obj) { - obj.Contract = null; + if (obj.AniDB_AnimeID == 0) + { + obj.Contract = null; + base.Save(obj); + } + using (var session = DatabaseFactory.SessionFactory.OpenSession()) + { + obj.UpdateContractDetailed(session.Wrap()); + } + // populate the database base.Save(obj); } - using (var session = DatabaseFactory.SessionFactory.OpenSession()) - { - obj.UpdateContractDetailed(session.Wrap()); - } - // populate the database - base.Save(obj); } } public SVR_AniDB_Anime GetByAnimeID(int id) { - return Animes.GetOne(id); + lock (Cache) + { + return Animes.GetOne(id); + } } public SVR_AniDB_Anime GetByAnimeID(ISessionWrapper session, int id) { - return Animes.GetOne(id); + lock (Cache) + { + return Animes.GetOne(id); + } } public List GetForDate(DateTime startDate, DateTime endDate) { - return Cache.Values.Where(a => a.AirDate.HasValue && a.AirDate.Value >= startDate && - a.AirDate.Value <= endDate) - .ToList(); - } - - public List GetForDate(ISession session, DateTime startDate, DateTime endDate) - { - return Cache.Values.Where(a => a.AirDate.HasValue && a.AirDate.Value >= startDate && - a.AirDate.Value <= endDate) - .ToList(); - } - - public List SearchByName(ISession session, string queryText) - { - return Cache.Values - .Where(a => a.AllTitles.IndexOf(queryText, StringComparison.InvariantCultureIgnoreCase) >= 0) - .ToList(); + lock (Cache) + { + return Cache.Values.Where(a => + a.AirDate.HasValue && a.AirDate.Value >= startDate && a.AirDate.Value <= endDate).ToList(); + } } public List SearchByName(string queryText) { - return Cache.Values - .Where(a => a.AllTitles.IndexOf(queryText, StringComparison.InvariantCultureIgnoreCase) >= 0) - .ToList(); + lock (Cache) + { + return Cache.Values.Where(a => + a.AllTitles.IndexOf(queryText, StringComparison.InvariantCultureIgnoreCase) >= 0).ToList(); + } } public List SearchByTag(string queryText) { - return Cache.Values.Where(a => a.AllTags.IndexOf(queryText, StringComparison.InvariantCultureIgnoreCase) >= - 0) - .ToList(); + lock (Cache) + { + return Cache.Values + .Where(a => a.AllTags.IndexOf(queryText, StringComparison.InvariantCultureIgnoreCase) >= 0) + .ToList(); + } } public Dictionary GetDefaultImagesByAnime(ISessionWrapper session, int[] animeIds) @@ -165,13 +161,12 @@ public Dictionary GetDefaultImagesByAnime(ISessionWrapp var defImagesByAnime = new Dictionary(); - if (animeIds.Length == 0) - { - return defImagesByAnime; - } + if (animeIds.Length == 0) return defImagesByAnime; - // TODO: Determine if joining on the correct columns - var results = session.CreateSQLQuery(@" + lock (globalDBLock) + { + // TODO: Determine if joining on the correct columns + var results = session.CreateSQLQuery(@" SELECT {defImg.*}, {tvWide.*}, {tvPoster.*}, {tvFanart.*}, {movPoster.*}, {movFanart.*} FROM AniDB_Anime_DefaultImage defImg LEFT OUTER JOIN TvDB_ImageWideBanner AS tvWide @@ -185,68 +180,69 @@ LEFT OUTER JOIN MovieDB_Poster AS movPoster LEFT OUTER JOIN MovieDB_Fanart AS movFanart ON movFanart.MovieDB_FanartID = defImg.ImageParentID AND defImg.ImageParentType = :movdbFanartType WHERE defImg.AnimeID IN (:animeIds) AND defImg.ImageParentType IN (:tvdbBannerType, :tvdbCoverType, :tvdbFanartType, :movdbPosterType, :movdbFanartType)") - .AddEntity("defImg", typeof(AniDB_Anime_DefaultImage)) - .AddEntity("tvWide", typeof(TvDB_ImageWideBanner)) - .AddEntity("tvPoster", typeof(TvDB_ImagePoster)) - .AddEntity("tvFanart", typeof(TvDB_ImageFanart)) - .AddEntity("movPoster", typeof(MovieDB_Poster)) - .AddEntity("movFanart", typeof(MovieDB_Fanart)) - .SetParameterList("animeIds", animeIds) - .SetInt32("tvdbBannerType", (int) ImageEntityType.TvDB_Banner) - .SetInt32("tvdbCoverType", (int) ImageEntityType.TvDB_Cover) - .SetInt32("tvdbFanartType", (int) ImageEntityType.TvDB_FanArt) - .SetInt32("movdbPosterType", (int) ImageEntityType.MovieDB_Poster) - .SetInt32("movdbFanartType", (int) ImageEntityType.MovieDB_FanArt) - .List(); - - foreach (object[] result in results) - { - var aniDbDefImage = (AniDB_Anime_DefaultImage) result[0]; - IImageEntity parentImage = null; - - switch ((ImageEntityType) aniDbDefImage.ImageParentType) + .AddEntity("defImg", typeof(AniDB_Anime_DefaultImage)) + .AddEntity("tvWide", typeof(TvDB_ImageWideBanner)) + .AddEntity("tvPoster", typeof(TvDB_ImagePoster)) + .AddEntity("tvFanart", typeof(TvDB_ImageFanart)) + .AddEntity("movPoster", typeof(MovieDB_Poster)) + .AddEntity("movFanart", typeof(MovieDB_Fanart)) + .SetParameterList("animeIds", animeIds) + .SetInt32("tvdbBannerType", (int) ImageEntityType.TvDB_Banner) + .SetInt32("tvdbCoverType", (int) ImageEntityType.TvDB_Cover) + .SetInt32("tvdbFanartType", (int) ImageEntityType.TvDB_FanArt) + .SetInt32("movdbPosterType", (int) ImageEntityType.MovieDB_Poster) + .SetInt32("movdbFanartType", (int) ImageEntityType.MovieDB_FanArt) + .List(); + + foreach (object[] result in results) { - case ImageEntityType.TvDB_Banner: - parentImage = (IImageEntity) result[1]; - break; - case ImageEntityType.TvDB_Cover: - parentImage = (IImageEntity) result[2]; - break; - case ImageEntityType.TvDB_FanArt: - parentImage = (IImageEntity) result[3]; - break; - case ImageEntityType.MovieDB_Poster: - parentImage = (IImageEntity) result[4]; - break; - case ImageEntityType.MovieDB_FanArt: - parentImage = (IImageEntity) result[5]; - break; - } + var aniDbDefImage = (AniDB_Anime_DefaultImage) result[0]; + IImageEntity parentImage = null; - if (parentImage == null) - { - continue; - } + switch ((ImageEntityType) aniDbDefImage.ImageParentType) + { + case ImageEntityType.TvDB_Banner: + parentImage = (IImageEntity) result[1]; + break; + case ImageEntityType.TvDB_Cover: + parentImage = (IImageEntity) result[2]; + break; + case ImageEntityType.TvDB_FanArt: + parentImage = (IImageEntity) result[3]; + break; + case ImageEntityType.MovieDB_Poster: + parentImage = (IImageEntity) result[4]; + break; + case ImageEntityType.MovieDB_FanArt: + parentImage = (IImageEntity) result[5]; + break; + } - DefaultAnimeImage defImage = new DefaultAnimeImage(aniDbDefImage, parentImage); + if (parentImage == null) + { + continue; + } - if (!defImagesByAnime.TryGetValue(aniDbDefImage.AnimeID, out DefaultAnimeImages defImages)) - { - defImages = new DefaultAnimeImages {AnimeID = aniDbDefImage.AnimeID}; - defImagesByAnime.Add(defImages.AnimeID, defImages); - } + DefaultAnimeImage defImage = new DefaultAnimeImage(aniDbDefImage, parentImage); - switch (defImage.AniDBImageSizeType) - { - case ImageSizeType.Poster: - defImages.Poster = defImage; - break; - case ImageSizeType.WideBanner: - defImages.WideBanner = defImage; - break; - case ImageSizeType.Fanart: - defImages.Fanart = defImage; - break; + if (!defImagesByAnime.TryGetValue(aniDbDefImage.AnimeID, out DefaultAnimeImages defImages)) + { + defImages = new DefaultAnimeImages {AnimeID = aniDbDefImage.AnimeID}; + defImagesByAnime.Add(defImages.AnimeID, defImages); + } + + switch (defImage.AniDBImageSizeType) + { + case ImageSizeType.Poster: + defImages.Poster = defImage; + break; + case ImageSizeType.WideBanner: + defImages.WideBanner = defImage; + break; + case ImageSizeType.Fanart: + defImages.Fanart = defImage; + break; + } } } diff --git a/Shoko.Server/Repositories/Cached/AniDB_Anime_TagRepository.cs b/Shoko.Server/Repositories/Cached/AniDB_Anime_TagRepository.cs index 5a454e5a1..b8085e80b 100644 --- a/Shoko.Server/Repositories/Cached/AniDB_Anime_TagRepository.cs +++ b/Shoko.Server/Repositories/Cached/AniDB_Anime_TagRepository.cs @@ -41,33 +41,19 @@ public override void PopulateIndexes() public AniDB_Anime_Tag GetByAnimeIDAndTagID(int animeid, int tagid) { - return Animes.GetMultiple(animeid).FirstOrDefault(a => a.TagID == tagid); - /* - using (var session = JMMService.SessionFactory.OpenSession()) + lock (Cache) { - AniDB_Anime_Tag cr = session - .CreateCriteria(typeof(AniDB_Anime_Tag)) - .Add(Restrictions.Eq("AnimeID", animeid)) - .Add(Restrictions.Eq("TagID", tagid)) - .UniqueResult(); - return cr; - }*/ + return Animes.GetMultiple(animeid).FirstOrDefault(a => a.TagID == tagid); + } } public List GetByAnimeID(int id) { - return Animes.GetMultiple(id); - /* - using (var session = JMMService.SessionFactory.OpenSession()) + lock (Cache) { - var tags = session - .CreateCriteria(typeof(AniDB_Anime_Tag)) - .Add(Restrictions.Eq("AnimeID", id)) - .List(); - - return new List(tags); - }*/ + return Animes.GetMultiple(id); + } } @@ -81,7 +67,10 @@ public ILookup GetByAnimeIDs(ICollection ids) return EmptyLookup.Instance; } - return ids.SelectMany(Animes.GetMultiple).ToLookup(t => t.AnimeID); + lock (Cache) + { + return ids.SelectMany(Animes.GetMultiple).ToLookup(t => t.AnimeID); + } } /// @@ -95,16 +84,6 @@ public List GetAllForLocalSeries() .Where(a => a != null) .Distinct() .ToList(); - /* - using (var session = JMMService.SessionFactory.OpenSession()) - { - var tags = - session.CreateQuery( - "FROM AniDB_Anime_Tag tag WHERE tag.AnimeID in (Select aser.AniDB_ID From AnimeSeries aser)") - .List(); - - return new List(tags); - }*/ } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Cached/AniDB_Anime_TitleRepository.cs b/Shoko.Server/Repositories/Cached/AniDB_Anime_TitleRepository.cs index 97b2e02f6..530ca8e6d 100644 --- a/Shoko.Server/Repositories/Cached/AniDB_Anime_TitleRepository.cs +++ b/Shoko.Server/Repositories/Cached/AniDB_Anime_TitleRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using NHibernate.Criterion; using NutzCode.InMemoryIndex; using Shoko.Commons.Collections; @@ -45,53 +46,45 @@ public override void RegenerateDb() public List GetByAnimeID(int id) { - return Animes.GetMultiple(id); + lock (Cache) + { + return Animes.GetMultiple(id); + } } - public ILookup GetByAnimeIDs(ISessionWrapper session, ICollection ids) + public ILookup GetByAnimeIDs([NotNull] ISessionWrapper session, ICollection ids) { if (session == null) - throw new ArgumentNullException("session"); + throw new ArgumentNullException(nameof(session)); if (ids == null) - throw new ArgumentNullException("ids"); + throw new ArgumentNullException(nameof(ids)); if (ids.Count == 0) { return EmptyLookup.Instance; } - var titles = session.CreateCriteria() - .Add(Restrictions.InG(nameof(AniDB_Anime_Title.AnimeID), ids)) - .List() - .ToLookup(t => t.AnimeID); + lock (globalDBLock) + { + var titles = session.CreateCriteria() + .Add(Restrictions.InG(nameof(AniDB_Anime_Title.AnimeID), ids)) + .List() + .ToLookup(t => t.AnimeID); - return titles; + return titles; + } } public List GetByAnimeIDLanguageTypeValue(int animeID, string language, string titleType, string titleValue) { - return - Animes.GetMultiple(animeID) - .Where( - a => - a.Language.Equals(language, StringComparison.InvariantCultureIgnoreCase) && - a.Title.Equals(titleValue, StringComparison.InvariantCultureIgnoreCase) && - a.TitleType.Equals(titleType, StringComparison.InvariantCultureIgnoreCase)) - .ToList(); - /* - using (var session = JMMService.SessionFactory.OpenSession()) + lock (Cache) { - var titles = session - .CreateCriteria(typeof(AniDB_Anime_Title)) - .Add(Restrictions.Eq("AnimeID", animeID)) - .Add(Restrictions.Eq("TitleType", titleType)) - .Add(Restrictions.Eq("Language", language)) - .Add(Restrictions.Eq("Title", titleValue)) - .List(); - - return new List(titles); - }*/ + return Animes.GetMultiple(animeID).Where(a => + a.Language.Equals(language, StringComparison.InvariantCultureIgnoreCase) && + a.Title.Equals(titleValue, StringComparison.InvariantCultureIgnoreCase) && + a.TitleType.Equals(titleType, StringComparison.InvariantCultureIgnoreCase)).ToList(); + } } /// @@ -100,22 +93,8 @@ public List GetByAnimeIDLanguageTypeValue(int animeID, string /// public List GetAllForLocalSeries() { - return - RepoFactory.AnimeSeries.GetAll() - .SelectMany(a => GetByAnimeID(a.AniDB_ID)) - .Where(a => a != null) - .Distinct() - .ToList(); - /* - using (var session = JMMService.SessionFactory.OpenSession()) - { - var titles = - session.CreateQuery( - "FROM AniDB_Anime_Title aat WHERE aat.AnimeID IN (Select aser.AniDB_ID From AnimeSeries aser)") - .List(); - - return new List(titles); - }*/ + return RepoFactory.AnimeSeries.GetAll().SelectMany(a => GetByAnimeID(a.AniDB_ID)).Where(a => a != null) + .Distinct().ToList(); } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Cached/AniDB_EpisodeRepository.cs b/Shoko.Server/Repositories/Cached/AniDB_EpisodeRepository.cs index 2f32e1de2..6a50ca47c 100644 --- a/Shoko.Server/Repositories/Cached/AniDB_EpisodeRepository.cs +++ b/Shoko.Server/Repositories/Cached/AniDB_EpisodeRepository.cs @@ -56,63 +56,38 @@ public override void RegenerateDb() public AniDB_Episode GetByEpisodeID(int id) { - return EpisodesIds.GetOne(id); - /* - using (var session = JMMService.SessionFactory.OpenSession()) + lock (Cache) { - AniDB_Episode cr = session - .CreateCriteria(typeof(AniDB_Episode)) - .Add(Restrictions.Eq("EpisodeID", id)) - .UniqueResult(); - return cr; - }*/ + return EpisodesIds.GetOne(id); + } } public List GetByAnimeID(int id) { - return Animes.GetMultiple(id); - /* - using (var session = JMMService.SessionFactory.OpenSession()) + lock (Cache) { - return GetByAnimeID(session, id); - }*/ + return Animes.GetMultiple(id); + } } public List GetByAnimeIDAndEpisodeNumber(int animeid, int epnumber) { - return Animes.GetMultiple(animeid) - .Where(a => a.EpisodeNumber == epnumber && a.GetEpisodeTypeEnum() == EpisodeType.Episode) - .ToList(); - /* - using (var session = JMMService.SessionFactory.OpenSession()) + lock (Cache) { - var eps = session - .CreateCriteria(typeof(AniDB_Episode)) - .Add(Restrictions.Eq("AnimeID", animeid)) - .Add(Restrictions.Eq("EpisodeNumber", epnumber)) - .Add(Restrictions.Eq("EpisodeType", (int) enEpisodeType.Episode)) - .List(); - - return new List(eps); - }*/ + return Animes.GetMultiple(animeid) + .Where(a => a.EpisodeNumber == epnumber && a.GetEpisodeTypeEnum() == EpisodeType.Episode) + .ToList(); + } } public List GetByAnimeIDAndEpisodeTypeNumber(int animeid, EpisodeType epType, int epnumber) { - return Animes.GetMultiple(animeid) - .Where(a => a.EpisodeNumber == epnumber && a.GetEpisodeTypeEnum() == epType) - .ToList(); -/* using (var session = JMMService.SessionFactory.OpenSession()) + lock (Cache) { - var eps = session - .CreateCriteria(typeof(AniDB_Episode)) - .Add(Restrictions.Eq("AnimeID", animeid)) - .Add(Restrictions.Eq("EpisodeNumber", epnumber)) - .Add(Restrictions.Eq("EpisodeType", (int) epType)) - .List(); - - return new List(eps); - }*/ + return Animes.GetMultiple(animeid) + .Where(a => a.EpisodeNumber == epnumber && a.GetEpisodeTypeEnum() == epType) + .ToList(); + } } public List GetEpisodesWithMultipleFiles() @@ -123,16 +98,6 @@ public List GetEpisodesWithMultipleFiles() .Where(a => a.Count() > 1) .Select(a => GetByEpisodeID(a.Key)) .ToList(); - /* - using (var session = JMMService.SessionFactory.OpenSession()) - { - var eps = - session.CreateQuery( - "FROM AniDB_Episode x WHERE x.EpisodeID IN (Select xref.EpisodeID FROM CrossRef_File_Episode xref GROUP BY xref.EpisodeID HAVING COUNT(xref.EpisodeID) > 1)") - .List(); - - return new List(eps); - }*/ } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Cached/AniDB_FileRepository.cs b/Shoko.Server/Repositories/Cached/AniDB_FileRepository.cs index 13a7111bc..d58e362e4 100644 --- a/Shoko.Server/Repositories/Cached/AniDB_FileRepository.cs +++ b/Shoko.Server/Repositories/Cached/AniDB_FileRepository.cs @@ -46,6 +46,7 @@ protected override int SelectKey(SVR_AniDB_File entity) public override void PopulateIndexes() { + // Only populated from main thread before these are accessible, so no lock Hashes = new PocoIndex(Cache, a => a.Hash); SHA1s = new PocoIndex(Cache, a => a.SHA1); MD5s = new PocoIndex(Cache, a => a.MD5); @@ -59,19 +60,9 @@ public override void RegenerateDb() { } - //Disable base saves. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("...", false)] public override void Save(SVR_AniDB_File obj) { - throw new NotSupportedException(); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("...", false)] - public override void Save(IReadOnlyCollection objs) - { - throw new NotSupportedException(); + Save(obj, true); } public void Save(SVR_AniDB_File obj, bool updateStats) @@ -87,122 +78,82 @@ public void Save(SVR_AniDB_File obj, bool updateStats) public SVR_AniDB_File GetByHash(string hash) { - return Hashes.GetOne(hash); - /* AniDB_File cr = session - .CreateCriteria(typeof(AniDB_File)) - .Add(Restrictions.Eq("Hash", hash)) - .UniqueResult(); - return cr;*/ + lock (Cache) + { + return Hashes.GetOne(hash); + } } public SVR_AniDB_File GetBySHA1(string hash) { - return SHA1s.GetOne(hash); - /* - AniDB_File cr = session - .CreateCriteria(typeof(AniDB_File)) - .Add(Restrictions.Eq("SHA1", hash)) - .UniqueResult(); - return cr;*/ + lock (Cache) + { + return SHA1s.GetOne(hash); + } } public SVR_AniDB_File GetByMD5(string hash) { - return MD5s.GetOne(hash); - /* - AniDB_File cr = session - .CreateCriteria(typeof(AniDB_File)) - .Add(Restrictions.Eq("MD5", hash)) - .UniqueResult(); - return cr;*/ + lock (Cache) + { + return MD5s.GetOne(hash); + } } public List GetByInternalVersion(int version) { - return InternalVersions.GetMultiple(version); - /* - AniDB_File cr = session - .CreateCriteria(typeof(AniDB_File)) - .Add(Restrictions.Eq("MD5", hash)) - .UniqueResult(); - return cr;*/ + lock (Cache) + { + return InternalVersions.GetMultiple(version); + } } public List GetWithWithMissingChapters() { - // the only containers that support chapters (and will have data on anidb) - List list = DatabaseFactory.SessionFactory.OpenSession() - .CreateSQLQuery( - @"SELECT FileID FROM AniDB_File WHERE IsChaptered = -1 AND (File_FileExtension = 'mkv' OR File_FileExtension = 'ogm')") - .List() - .Select(GetByFileID) - .ToList(); - return list; - /* - AniDB_File cr = session - .CreateCriteria(typeof(AniDB_File)) - .Add(Restrictions.Eq("MD5", hash)) - .UniqueResult(); - return cr;*/ + lock (globalDBLock) + { + // the only containers that support chapters (and will have data on anidb) + List list = DatabaseFactory.SessionFactory.OpenSession() + .CreateSQLQuery( + @"SELECT FileID FROM AniDB_File WHERE IsChaptered = -1 AND (File_FileExtension = 'mkv' OR File_FileExtension = 'ogm')") + .List() + .Select(GetByFileID) + .ToList(); + return list; + } } public SVR_AniDB_File GetByHashAndFileSize(string hash, long fsize) { - return Hashes.GetMultiple(hash).FirstOrDefault(a => a.FileSize == fsize); - /*using (var session = JMMService.SessionFactory.OpenSession()) + lock (Cache) { - AniDB_File cr = session - .CreateCriteria(typeof(AniDB_File)) - .Add(Restrictions.Eq("Hash", hash)) - .Add(Restrictions.Eq("FileSize", fsize)) - .UniqueResult(); - return cr; - }*/ + return Hashes.GetMultiple(hash).FirstOrDefault(a => a.FileSize == fsize); + } } public SVR_AniDB_File GetByFileID(int fileID) { - return FileIds.GetOne(fileID); - /* - using (var session = JMMService.SessionFactory.OpenSession()) + lock (Cache) { - AniDB_File cr = session - .CreateCriteria(typeof(AniDB_File)) - .Add(Restrictions.Eq("FileID", fileID)) - .UniqueResult(); - return cr; - }*/ + return FileIds.GetOne(fileID); + } } public List GetByAnimeID(int animeID) { - return Animes.GetMultiple(animeID); - /* - using (var session = JMMService.SessionFactory.OpenSession()) + lock (Cache) { - var objs = session - .CreateCriteria(typeof(AniDB_File)) - .Add(Restrictions.Eq("AnimeID", animeID)) - .List(); - - return new List(objs); - }*/ + return Animes.GetMultiple(animeID); + } } public List GetByResolution(string res) { - return Resolutions.GetMultiple(res); - /* - using (var session = JMMService.SessionFactory.OpenSession()) + lock (Cache) { - var objs = session - .CreateCriteria(typeof(AniDB_File)) - .Add(Restrictions.Eq("File_VideoResolution", res)) - .List(); - - return new List(objs); - }*/ + return Resolutions.GetMultiple(res); + } } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Cached/AniDB_TagRepository.cs b/Shoko.Server/Repositories/Cached/AniDB_TagRepository.cs index cb0bb20e5..3bdc27c62 100644 --- a/Shoko.Server/Repositories/Cached/AniDB_TagRepository.cs +++ b/Shoko.Server/Repositories/Cached/AniDB_TagRepository.cs @@ -27,10 +27,10 @@ private AniDB_TagRepository() public override void RegenerateDb() { - List tags = Cache.Values.Where(tag => tag.TagDescription.Contains('`') || tag.TagName.Contains('`')).ToList(); + List tags = Cache.Values.Where(tag => (tag.TagDescription?.Contains('`') ?? false) || tag.TagName.Contains('`')).ToList(); foreach (AniDB_Tag tag in tags) { - tag.TagDescription = tag.TagDescription.Replace('`', '\''); + tag.TagDescription = tag.TagDescription?.Replace('`', '\''); tag.TagName = tag.TagName.Replace('`', '\''); Save(tag); } @@ -60,7 +60,13 @@ public ILookup GetByAnimeIDs(int[] ids) } - public AniDB_Tag GetByTagID(int id) => Tags.GetOne(id); + public AniDB_Tag GetByTagID(int id) + { + lock (Cache) + { + return Tags.GetOne(id); + } + } /// diff --git a/Shoko.Server/Repositories/Cached/AniDB_VoteRepository.cs b/Shoko.Server/Repositories/Cached/AniDB_VoteRepository.cs index 008e57b32..696581839 100644 --- a/Shoko.Server/Repositories/Cached/AniDB_VoteRepository.cs +++ b/Shoko.Server/Repositories/Cached/AniDB_VoteRepository.cs @@ -47,34 +47,44 @@ private AniDB_VoteRepository() public AniDB_Vote GetByEntityAndType(int entID, AniDBVoteType voteType) { - List cr = EntityIDs.GetMultiple(entID)?.Where(a => a.VoteType == (int) voteType).ToList(); + lock (Cache) + { + List cr = EntityIDs.GetMultiple(entID)?.Where(a => a.VoteType == (int) voteType).ToList(); - if (cr.Count <= 1) return cr.FirstOrDefault(); + if (cr == null) return null; + if (cr.Count <= 1) return cr.FirstOrDefault(); - using (var session = DatabaseFactory.SessionFactory.OpenSession()) - { - bool first = true; - foreach (AniDB_Vote dbVote in cr) + lock (globalDBLock) { - if (first) - { - first = false; - continue; - } - using (var transact = session.BeginTransaction()) + using (var session = DatabaseFactory.SessionFactory.OpenSession()) { - RepoFactory.AniDB_Vote.DeleteWithOpenTransaction(session, dbVote); - transact.Commit(); + bool first = true; + foreach (AniDB_Vote dbVote in cr) + { + if (first) + { + first = false; + continue; + } + using (var transact = session.BeginTransaction()) + { + RepoFactory.AniDB_Vote.DeleteWithOpenTransaction(session, dbVote); + transact.Commit(); + } + } + + return cr.FirstOrDefault(); } } - - return cr.FirstOrDefault(); } } public List GetByEntity(int entID) { - return EntityIDs.GetMultiple(entID)?.ToList(); + lock (Cache) + { + return EntityIDs.GetMultiple(entID)?.ToList(); + } } public AniDB_Vote GetByAnimeID(int animeID) diff --git a/Shoko.Server/Repositories/Cached/AnimeEpisodeRepository.cs b/Shoko.Server/Repositories/Cached/AnimeEpisodeRepository.cs index 4fad8d4dd..04d48aeb5 100644 --- a/Shoko.Server/Repositories/Cached/AnimeEpisodeRepository.cs +++ b/Shoko.Server/Repositories/Cached/AnimeEpisodeRepository.cs @@ -5,6 +5,7 @@ using Shoko.Models.Server; using NHibernate; using NutzCode.InMemoryIndex; +using Pri.LongPath; using Shoko.Server.Databases; using Shoko.Server.Models; using Shoko.Server.PlexAndKodi; @@ -51,13 +52,6 @@ private void UpdatePlexContract(SVR_AnimeEpisode e) e.PlexContract = Helper.GenerateVideoFromAnimeEpisode(e); } - - public override void Save(IReadOnlyCollection objs) - { - foreach (SVR_AnimeEpisode ep in objs) - Save(ep); - } - public override void Save(SVR_AnimeEpisode obj) { lock (obj) @@ -75,14 +69,20 @@ public override void Save(SVR_AnimeEpisode obj) public List GetBySeriesID(int seriesid) { - return Series.GetMultiple(seriesid); + lock (Cache) + { + return Series.GetMultiple(seriesid); + } } public SVR_AnimeEpisode GetByAniDBEpisodeID(int epid) { - //AniDB_Episode may not unique for the series, Example with Toriko Episode 1 and One Piece 492, same AniDBEpisodeID in two shows. - return EpisodeIDs.GetOne(epid); + lock (Cache) + { + //AniDB_Episode may not unique for the series, Example with Toriko Episode 1 and One Piece 492, same AniDBEpisodeID in two shows. + return EpisodeIDs.GetOne(epid); + } } @@ -94,7 +94,7 @@ public SVR_AnimeEpisode GetByAniDBEpisodeID(int epid) public SVR_AnimeEpisode GetByFilename(string name) { return RepoFactory.VideoLocalPlace.GetAll() - .Where(v => name.Equals(v.FilePath.Split('\\').LastOrDefault(), + .Where(v => name.Equals(v.FilePath.Split(Path.DirectorySeparatorChar).LastOrDefault(), StringComparison.InvariantCultureIgnoreCase)) .Select(a => a.VideoLocal.GetAnimeEpisodes()) .FirstOrDefault() @@ -116,82 +116,24 @@ public List GetByHash(string hash) .Select(a => GetByAniDBEpisodeID(a.EpisodeID)) .Where(a => a != null) .ToList(); - /* - return - session.CreateQuery( - "Select ae.AnimeEpisodeID FROM AnimeEpisode as ae, CrossRef_File_Episode as xref WHERE ae.AniDB_EpisodeID = xref.EpisodeID AND xref.Hash= :Hash") - .SetParameter("Hash", hash) - .List().Select(GetByID).Where(a => a != null).ToList();*/ } public List GetEpisodesWithMultipleFiles(bool ignoreVariations) { - string ignoreVariationsQuery = - @"SELECT ani.EpisodeID FROM VideoLocal AS vl JOIN CrossRef_File_Episode ani ON vl.Hash = ani.Hash WHERE vl.IsVariation = 0 AND vl.Hash != '' GROUP BY ani.EpisodeID HAVING COUNT(ani.EpisodeID) > 1"; - string countVariationsQuery = - @"SELECT ani.EpisodeID FROM VideoLocal AS vl JOIN CrossRef_File_Episode ani ON vl.Hash = ani.Hash WHERE vl.Hash != '' GROUP BY ani.EpisodeID HAVING COUNT(ani.EpisodeID) > 1"; - using (var session = DatabaseFactory.SessionFactory.OpenSession()) - { - IList ids = ignoreVariations - ? session.CreateSQLQuery(ignoreVariationsQuery).List() - : session.CreateSQLQuery(countVariationsQuery).List(); - return ids.Select(GetByAniDBEpisodeID).ToList(); - } - /* - - using (var session = JMMService.SessionFactory.OpenSession()) + lock (globalDBLock) { - //FROM AnimeEpisode x WHERE x.AniDB_EpisodeID IN (Select xref.EpisodeID FROM CrossRef_File_Episode xref WHERE xref.Hash IN (Select vl.Hash from VideoLocal vl) GROUP BY xref.EpisodeID HAVING COUNT(xref.EpisodeID) > 1) - - - //FROM AnimeEpisode x INNER JOIN (select xref.EpisodeID as EpisodeID from CrossRef_File_Episode xref inner join VideoLocal vl ON xref.Hash = vl.Hash group by xref.EpisodeID having count(xref.EpisodeID)>1) g ON g.EpisodeID = x.AniDB_EpisodeID - - if (ServerSettings.DatabaseType.Trim() - .Equals(Constants.DatabaseType.MySQL, StringComparison.InvariantCultureIgnoreCase)) + string ignoreVariationsQuery = + @"SELECT ani.EpisodeID FROM VideoLocal AS vl JOIN CrossRef_File_Episode ani ON vl.Hash = ani.Hash WHERE vl.IsVariation = 0 AND vl.Hash != '' GROUP BY ani.EpisodeID HAVING COUNT(ani.EpisodeID) > 1"; + string countVariationsQuery = + @"SELECT ani.EpisodeID FROM VideoLocal AS vl JOIN CrossRef_File_Episode ani ON vl.Hash = ani.Hash WHERE vl.Hash != '' GROUP BY ani.EpisodeID HAVING COUNT(ani.EpisodeID) > 1"; + using (var session = DatabaseFactory.SessionFactory.OpenSession()) { - // work around for MySQL performance issue when handling sub queries - List epList = new List(); - string sql = "Select x.AnimeEpisodeID " + - "FROM AnimeEpisode x " + - "INNER JOIN " + - "(select xref.EpisodeID as EpisodeID " + - "from CrossRef_File_Episode xref " + - "inner join VideoLocal vl ON xref.Hash = vl.Hash "; - - if (ignoreVariations) - sql += " where IsVariation = 0 "; - - sql += "group by xref.EpisodeID having count(xref.EpisodeID)>1) " + - "g ON g.EpisodeID = x.AniDB_EpisodeID " + - " "; - ArrayList results = DatabaseExtensions.Instance.GetData(sql); - - foreach (object[] res in results) - { - int animeEpisodeID = int.Parse(res[0].ToString()); - AnimeEpisode ep = GetByID(animeEpisodeID); - if (ep != null) - epList.Add(ep); - } - - return epList; + IList ids = ignoreVariations + ? session.CreateSQLQuery(ignoreVariationsQuery).List() + : session.CreateSQLQuery(countVariationsQuery).List(); + return ids.Select(GetByAniDBEpisodeID).ToList(); } - else - { - string sql = "SELECT x.AnimeEpisodeID FROM AnimeEpisode x WHERE x.AniDB_EpisodeID IN " + - "(Select xref.EpisodeID FROM CrossRef_File_Episode xref WHERE xref.Hash IN " + - "(Select vl.Hash from VideoLocal vl "; - - if (ignoreVariations) - sql += " where IsVariation = 0 "; - - sql += ") GROUP BY xref.EpisodeID HAVING COUNT(xref.EpisodeID) > 1)"; - - return session.CreateQuery(sql).List().Select(GetByID).Where(a => a != null).ToList(); - ; - } - - }*/ + } } public List GetUnwatchedEpisodes(int seriesid, int userid) @@ -202,16 +144,6 @@ public List GetUnwatchedEpisodes(int seriesid, int userid) .Select(a => a.AnimeEpisodeID) .ToList(); return GetBySeriesID(seriesid).Where(a => !eps.Contains(a.AnimeEpisodeID)).ToList(); - /* - using (var session = JMMService.SessionFactory.OpenSession()) - { - return - session.CreateQuery( - "SELECT x.AnimeEpisodeID FROM AnimeEpisode x WHERE x.AnimeEpisodeID NOT IN (SELECT AnimeEpisodeID FROM AnimeEpisode_User WHERE AnimeSeriesID = :AnimeSeriesID AND JMMUserID = :JMMUserID) AND x.AnimeSeriesID = :AnimeSeriesID") - .SetParameter("AnimeSeriesID", seriesid) - .SetParameter("JMMUserID", userid) - .List().Select(GetByID).Where(a => a != null).ToList(); - }*/ } public List GetMostRecentlyAdded(int seriesID) diff --git a/Shoko.Server/Repositories/Cached/AnimeEpisode_UserRepository.cs b/Shoko.Server/Repositories/Cached/AnimeEpisode_UserRepository.cs index 9428c6502..570fdebe3 100644 --- a/Shoko.Server/Repositories/Cached/AnimeEpisode_UserRepository.cs +++ b/Shoko.Server/Repositories/Cached/AnimeEpisode_UserRepository.cs @@ -60,32 +60,21 @@ public override void RegenerateDb() Save(g); cnt++; if (cnt % 10 == 0) - { ServerState.Instance.CurrentSetupStatus = string.Format( Commons.Properties.Resources.Database_Cache, typeof(AnimeEpisode_User).Name, " DbRegen - " + cnt + "/" + max); - } } ServerState.Instance.CurrentSetupStatus = string.Format(Commons.Properties.Resources.Database_Cache, typeof(AnimeEpisode_User).Name, " DbRegen - " + max + "/" + max); } - - public override void Save(IReadOnlyCollection objs) - { - foreach (SVR_AnimeEpisode_User e in objs) - Save(e); - } - public override void Save(SVR_AnimeEpisode_User obj) { lock (obj) { if (obj.AnimeEpisode_UserID == 0) - { base.Save(obj); - } UpdateContract(obj); base.Save(obj); } @@ -96,9 +85,7 @@ public override void SaveWithOpenTransaction(ISessionWrapper session, SVR_AnimeE lock (obj) { if (obj.AnimeEpisode_UserID == 0) - { base.SaveWithOpenTransaction(session, obj); - } UpdateContract(obj); base.SaveWithOpenTransaction(session, obj); } @@ -106,53 +93,68 @@ public override void SaveWithOpenTransaction(ISessionWrapper session, SVR_AnimeE public List GetBySeriesID(int seriesid) { - return Series.GetMultiple(seriesid); + lock (Cache) + { + return Series.GetMultiple(seriesid); + } } public SVR_AnimeEpisode_User GetByUserIDAndEpisodeID(int userid, int epid) { - return UsersEpisodes.GetOne(userid, epid); + lock (Cache) + { + return UsersEpisodes.GetOne(userid, epid); + } } public List GetByUserID(int userid) { - return Users.GetMultiple(userid); + lock (Cache) + { + return Users.GetMultiple(userid); + } } public List GetMostRecentlyWatched(int userid, int maxresults = 100) { - return - GetByUserID(userid) - .Where(a => a.WatchedCount > 0) - .OrderByDescending(a => a.WatchedDate) - .Take(maxresults) - .ToList(); + return GetByUserID(userid).Where(a => a.WatchedCount > 0).OrderByDescending(a => a.WatchedDate) + .Take(maxresults).ToList(); } public SVR_AnimeEpisode_User GetLastWatchedEpisode() { - return Cache.Values.Where(a => a.WatchedCount > 0).OrderByDescending(a => a.WatchedDate).FirstOrDefault(); + lock (Cache) + { + return Cache.Values.Where(a => a.WatchedCount > 0).OrderByDescending(a => a.WatchedDate) + .FirstOrDefault(); + } } public SVR_AnimeEpisode_User GetLastWatchedEpisodeForSeries(int seriesid, int userid) { - return - UsersSeries.GetMultiple(userid, seriesid) - .Where(a => a.WatchedCount > 0) - .OrderByDescending(a => a.WatchedDate) - .FirstOrDefault(); + lock (Cache) + { + return UsersSeries.GetMultiple(userid, seriesid).Where(a => a.WatchedCount > 0) + .OrderByDescending(a => a.WatchedDate).FirstOrDefault(); + } } public List GetByEpisodeID(int epid) { - return Episodes.GetMultiple(epid); + lock (Cache) + { + return Episodes.GetMultiple(epid); + } } public List GetByUserIDAndSeriesID(int userid, int seriesid) { - return UsersSeries.GetMultiple(userid, seriesid); + lock (Cache) + { + return UsersSeries.GetMultiple(userid, seriesid); + } } diff --git a/Shoko.Server/Repositories/Cached/AnimeGroupRepository.cs b/Shoko.Server/Repositories/Cached/AnimeGroupRepository.cs index ba1d086cc..af090a518 100644 --- a/Shoko.Server/Repositories/Cached/AnimeGroupRepository.cs +++ b/Shoko.Server/Repositories/Cached/AnimeGroupRepository.cs @@ -86,20 +86,9 @@ public override void RegenerateDb() " DbRegen - " + max + "/" + max); } - - //Disable base saves. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("...", false)] public override void Save(SVR_AnimeGroup obj) { - throw new NotSupportedException(); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("...", false)] - public override void Save(IReadOnlyCollection objs) - { - throw new NotSupportedException(); + Save(obj, true, true); } public void Save(SVR_AnimeGroup grp, bool updategrpcontractstats, bool recursive, @@ -109,26 +98,32 @@ public void Save(SVR_AnimeGroup grp, bool updategrpcontractstats, bool recursive { ISessionWrapper sessionWrapper = session.Wrap(); HashSet types; - lock (grp) + lock (globalDBLock) { - if (grp.AnimeGroupID == 0) - //We are creating one, and we need the AnimeGroupID before Update the contracts + lock (grp) { - grp.Contract = null; + if (grp.AnimeGroupID == 0) + //We are creating one, and we need the AnimeGroupID before Update the contracts + { + grp.Contract = null; + using (var transaction = session.BeginTransaction()) + { + session.SaveOrUpdate(grp); + transaction.Commit(); + } + } + types = grp.UpdateContract(sessionWrapper, updategrpcontractstats); + //Types will contains the affected GroupFilterConditionTypes using (var transaction = session.BeginTransaction()) { - session.SaveOrUpdate(grp); + SaveWithOpenTransaction(session, grp); transaction.Commit(); } + lock (Changes) + { + Changes.AddOrUpdate(grp.AnimeGroupID); + } } - types = grp.UpdateContract(sessionWrapper, updategrpcontractstats); - //Types will contains the affected GroupFilterConditionTypes - using (var transaction = session.BeginTransaction()) - { - base.SaveWithOpenTransaction(session, grp); - transaction.Commit(); - } - Changes.AddOrUpdate(grp.AnimeGroupID); } if (verifylockedFilters) { @@ -157,10 +152,19 @@ public void InsertBatch(ISessionWrapper session, IReadOnlyCollection g.AnimeGroupID)); + lock (Changes) + { + Changes.AddOrUpdateRange(groups.Select(g => g.AnimeGroupID)); + } } public void UpdateBatch(ISessionWrapper session, IReadOnlyCollection groups) @@ -172,10 +176,19 @@ public void UpdateBatch(ISessionWrapper session, IReadOnlyCollection g.AnimeGroupID)); + lock (Changes) + { + Changes.AddOrUpdateRange(groups.Select(g => g.AnimeGroupID)); + } } /// @@ -195,21 +208,31 @@ public void DeleteAll(ISessionWrapper session, int? excludeGroupId = null) // First, get all of the current groups so that we can inform the change tracker that they have been removed later var allGrps = GetAll(); - // Then, actually delete the AnimeGroups - if (excludeGroupId != null) + lock (globalDBLock) { - session.CreateQuery("delete SVR_AnimeGroup ag where ag.id <> :excludeId") - .SetInt32("excludeId", excludeGroupId.Value) - .ExecuteUpdate(); + // Then, actually delete the AnimeGroups + if (excludeGroupId != null) + { + session.CreateQuery("delete SVR_AnimeGroup ag where ag.id <> :excludeId") + .SetInt32("excludeId", excludeGroupId.Value) + .ExecuteUpdate(); - Changes.RemoveRange(allGrps.Select(g => g.AnimeGroupID).Where(id => id != excludeGroupId.Value)); - } - else - { - session.CreateQuery("delete SVR_AnimeGroup ag") - .ExecuteUpdate(); + lock (Changes) + { + Changes.RemoveRange(allGrps.Select(g => g.AnimeGroupID) + .Where(id => id != excludeGroupId.Value)); + } + } + else + { + session.CreateQuery("delete SVR_AnimeGroup ag") + .ExecuteUpdate(); - Changes.RemoveRange(allGrps.Select(g => g.AnimeGroupID)); + lock (Changes) + { + Changes.RemoveRange(allGrps.Select(g => g.AnimeGroupID)); + } + } } // Finally, we need to clear the cache so that it is in sync with the database @@ -222,24 +245,36 @@ public void DeleteAll(ISessionWrapper session, int? excludeGroupId = null) if (excludedGroup != null) { - Cache.Update(excludedGroup); + lock (Cache) + { + Cache.Update(excludedGroup); + } } } } public List GetByParentID(int parentid) { - return Parents.GetMultiple(parentid); + lock (Cache) + { + return Parents.GetMultiple(parentid); + } } public List GetAllTopLevelGroups() { - return Parents.GetMultiple(0); + lock (Cache) + { + return Parents.GetMultiple(0); + } } public ChangeTracker GetChangeTracker() { - return Changes; + lock (Changes) + { + return Changes; + } } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Cached/AnimeGroup_UserRepository.cs b/Shoko.Server/Repositories/Cached/AnimeGroup_UserRepository.cs index 3e77594e2..0411da348 100644 --- a/Shoko.Server/Repositories/Cached/AnimeGroup_UserRepository.cs +++ b/Shoko.Server/Repositories/Cached/AnimeGroup_UserRepository.cs @@ -29,9 +29,12 @@ private AnimeGroup_UserRepository() { EndDeleteCallback = (cr) => { - if (!Changes.ContainsKey(cr.JMMUserID)) - Changes[cr.JMMUserID] = new ChangeTracker(); - Changes[cr.JMMUserID].Remove(cr.AnimeGroupID); + lock (Changes) + { + if (!Changes.ContainsKey(cr.JMMUserID)) + Changes[cr.JMMUserID] = new ChangeTracker(); + Changes[cr.JMMUserID].Remove(cr.AnimeGroupID); + } logger.Trace("Updating group filter stats by animegroup from AnimeGroup_UserRepository.Delete: {0}", cr.AnimeGroupID); cr.DeleteFromFilters(); @@ -65,13 +68,6 @@ public override void RegenerateDb() { } - public override void Save(IReadOnlyCollection objs) - { - foreach (SVR_AnimeGroup_User grp in objs) - Save(grp); - } - - public override void Save(SVR_AnimeGroup_User obj) { lock (obj) @@ -85,9 +81,12 @@ public override void Save(SVR_AnimeGroup_User obj) } HashSet types = SVR_AnimeGroup_User.GetConditionTypesChanged(old, obj); base.Save(obj); - if (!Changes.ContainsKey(obj.JMMUserID)) - Changes[obj.JMMUserID] = new ChangeTracker(); - Changes[obj.JMMUserID].AddOrUpdate(obj.AnimeGroupID); + lock (Changes) + { + if (!Changes.ContainsKey(obj.JMMUserID)) + Changes[obj.JMMUserID] = new ChangeTracker(); + Changes[obj.JMMUserID].AddOrUpdate(obj.AnimeGroupID); + } obj.UpdateGroupFilter(types); } } @@ -112,16 +111,20 @@ public void InsertBatch(ISessionWrapper session, IEnumerable changeTracker)) + lock (globalDBLock) { - changeTracker = new ChangeTracker(); - Changes[groupUser.JMMUserID] = changeTracker; + session.Insert(groupUser); } - changeTracker.AddOrUpdate(groupUser.AnimeGroupID); + lock (Changes) + { + if (!Changes.TryGetValue(groupUser.JMMUserID, out ChangeTracker changeTracker)) + { + changeTracker = new ChangeTracker(); + Changes[groupUser.JMMUserID] = changeTracker; + } + changeTracker.AddOrUpdate(groupUser.AnimeGroupID); + } } } @@ -144,16 +147,21 @@ public void UpdateBatch(ISessionWrapper session, IEnumerable changeTracker)) + lock (globalDBLock) { - changeTracker = new ChangeTracker(); - Changes[groupUser.JMMUserID] = changeTracker; + session.Update(groupUser); } - changeTracker.AddOrUpdate(groupUser.AnimeGroupID); + lock (Changes) + { + if (!Changes.TryGetValue(groupUser.JMMUserID, out ChangeTracker changeTracker)) + { + changeTracker = new ChangeTracker(); + Changes[groupUser.JMMUserID] = changeTracker; + } + + changeTracker.AddOrUpdate(groupUser.AnimeGroupID); + } } } @@ -174,22 +182,27 @@ public void DeleteAll(ISessionWrapper session) var usrGrpMap = GetAll() .GroupBy(g => g.JMMUserID, g => g.AnimeGroupID); - // Then, actually delete the AnimeGroup_Users - session.CreateQuery("delete SVR_AnimeGroup_User agu") - .ExecuteUpdate(); + lock (globalDBLock) + { + // Then, actually delete the AnimeGroup_Users + session.CreateQuery("delete SVR_AnimeGroup_User agu").ExecuteUpdate(); + } // Now, update the change trackers with all removed records foreach (var grp in usrGrpMap) { int jmmUserId = grp.Key; - if (!Changes.TryGetValue(jmmUserId, out ChangeTracker changeTracker)) + lock (Changes) { - changeTracker = new ChangeTracker(); - Changes[jmmUserId] = changeTracker; - } + if (!Changes.TryGetValue(jmmUserId, out ChangeTracker changeTracker)) + { + changeTracker = new ChangeTracker(); + Changes[jmmUserId] = changeTracker; + } - changeTracker.RemoveRange(grp); + changeTracker.RemoveRange(grp); + } } // Finally, we need to clear the cache so that it is in sync with the database @@ -198,23 +211,35 @@ public void DeleteAll(ISessionWrapper session) public SVR_AnimeGroup_User GetByUserAndGroupID(int userid, int groupid) { - return UsersGroups.GetOne(userid, groupid); + lock (Cache) + { + return UsersGroups.GetOne(userid, groupid); + } } public List GetByUserID(int userid) { - return Users.GetMultiple(userid); + lock (Cache) + { + return Users.GetMultiple(userid); + } } public List GetByGroupID(int groupid) { - return Groups.GetMultiple(groupid); + lock (Cache) + { + return Groups.GetMultiple(groupid); + } } public ChangeTracker GetChangeTracker(int userid) { - if (Changes.ContainsKey(userid)) - return Changes[userid]; + lock (Changes) + { + if (Changes.ContainsKey(userid)) + return Changes[userid]; + } return new ChangeTracker(); } } diff --git a/Shoko.Server/Repositories/Cached/AnimeSeriesRepository.cs b/Shoko.Server/Repositories/Cached/AnimeSeriesRepository.cs index f1374fd23..ba82915bd 100644 --- a/Shoko.Server/Repositories/Cached/AnimeSeriesRepository.cs +++ b/Shoko.Server/Repositories/Cached/AnimeSeriesRepository.cs @@ -29,7 +29,10 @@ private AnimeSeriesRepository() BeginDeleteCallback = (cr) => { RepoFactory.AnimeSeries_User.Delete(RepoFactory.AnimeSeries_User.GetBySeriesID(cr.AnimeSeriesID)); - Changes.Remove(cr.AnimeSeriesID); + lock (Changes) + { + Changes.Remove(cr.AnimeSeriesID); + } }; EndDeleteCallback = (cr) => { @@ -105,28 +108,16 @@ public override void RegenerateDb() } } - public ChangeTracker GetChangeTracker() { return Changes; } - //Disable base saves. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("...", false)] public override void Save(SVR_AnimeSeries obj) { - throw new NotSupportedException(); + Save(obj, false); } - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("...", false)] - public override void Save(IReadOnlyCollection objs) - { - throw new NotSupportedException(); - } - - public void Save(SVR_AnimeSeries obj, bool onlyupdatestats) { Save(obj, true, onlyupdatestats); @@ -148,7 +139,10 @@ public void Save(SVR_AnimeSeries obj, bool updateGroups, bool onlyupdatestats, b SVR_AnimeSeries oldSeries; using (var session = DatabaseFactory.SessionFactory.OpenSession()) { - oldSeries = session.Get(obj.AnimeSeriesID); + lock (globalDBLock) + { + oldSeries = session.Get(obj.AnimeSeriesID); + } } if (oldSeries != null) { @@ -185,7 +179,10 @@ public void Save(SVR_AnimeSeries obj, bool updateGroups, bool onlyupdatestats, b //This call will create extra years or tags if the Group have a new year or tag obj.UpdateGroupFilters(types, null); } - Changes.AddOrUpdate(obj.AnimeSeriesID); + lock (Changes) + { + Changes.AddOrUpdate(obj.AnimeSeriesID); + } } if (updateGroups && !isMigrating) { @@ -222,14 +219,23 @@ public void UpdateBatch(ISessionWrapper session, IReadOnlyCollection { - if (!Changes.ContainsKey(cr.JMMUserID)) - Changes[cr.JMMUserID] = new ChangeTracker(); - Changes[cr.JMMUserID].Remove(cr.AnimeSeriesID); + lock (Changes) + { + if (!Changes.ContainsKey(cr.JMMUserID)) + Changes[cr.JMMUserID] = new ChangeTracker(); + Changes[cr.JMMUserID].Remove(cr.AnimeSeriesID); + } cr.DeleteFromFilters(); }; } @@ -59,28 +62,27 @@ public override void RegenerateDb() } - public override void Save(IReadOnlyCollection objs) - { - foreach (SVR_AnimeSeries_User s in objs) - Save(s); - } - - public override void Save(SVR_AnimeSeries_User obj) { lock (obj) { UpdatePlexKodiContracts(obj); SVR_AnimeSeries_User old; - using (var session = DatabaseFactory.SessionFactory.OpenSession()) + lock (globalDBLock) { - old = session.Get(obj.AnimeSeries_UserID); + using (var session = DatabaseFactory.SessionFactory.OpenSession()) + { + old = session.Get(obj.AnimeSeries_UserID); + } } HashSet types = SVR_AnimeSeries_User.GetConditionTypesChanged(old, obj); base.Save(obj); - if (!Changes.ContainsKey(obj.JMMUserID)) - Changes[obj.JMMUserID] = new ChangeTracker(); - Changes[obj.JMMUserID].AddOrUpdate(obj.AnimeSeriesID); + lock (Changes) + { + if (!Changes.ContainsKey(obj.JMMUserID)) + Changes[obj.JMMUserID] = new ChangeTracker(); + Changes[obj.JMMUserID].AddOrUpdate(obj.AnimeSeriesID); + } obj.UpdateGroupFilter(types); } //logger.Trace("Updating group stats by series from AnimeSeries_UserRepository.Save: {0}", obj.AnimeSeriesID); @@ -89,31 +91,36 @@ public override void Save(SVR_AnimeSeries_User obj) private void UpdatePlexKodiContracts(SVR_AnimeSeries_User ugrp) { - using (var session = DatabaseFactory.SessionFactory.OpenSession()) - { - SVR_AnimeSeries ser = RepoFactory.AnimeSeries.GetByID(ugrp.AnimeSeriesID); - CL_AnimeSeries_User con = ser?.GetUserContract(ugrp.JMMUserID); - if (con == null) - return; - ugrp.PlexContract = Helper.GenerateFromSeries(con, ser, ser.GetAnime(), ugrp.JMMUserID); - } + SVR_AnimeSeries ser = RepoFactory.AnimeSeries.GetByID(ugrp.AnimeSeriesID); + CL_AnimeSeries_User con = ser?.GetUserContract(ugrp.JMMUserID); + if (con == null) + return; + ugrp.PlexContract = Helper.GenerateFromSeries(con, ser, ser.GetAnime(), ugrp.JMMUserID); } public SVR_AnimeSeries_User GetByUserAndSeriesID(int userid, int seriesid) { - return UsersSeries.GetOne(userid, seriesid); + lock (Cache) + { + return UsersSeries.GetOne(userid, seriesid); + } } - public List GetByUserID(int userid) { - return Users.GetMultiple(userid); + lock (Cache) + { + return Users.GetMultiple(userid); + } } public List GetBySeriesID(int seriesid) { - return Series.GetMultiple(seriesid); + lock (Cache) + { + return Series.GetMultiple(seriesid); + } } @@ -129,8 +136,11 @@ public List GetMostRecentlyWatched(int userID) public ChangeTracker GetChangeTracker(int userid) { - if (Changes.ContainsKey(userid)) - return Changes[userid]; + lock (Changes) + { + if (Changes.ContainsKey(userid)) + return Changes[userid]; + } return new ChangeTracker(); } } diff --git a/Shoko.Server/Repositories/Cached/AuthTokensRepository.cs b/Shoko.Server/Repositories/Cached/AuthTokensRepository.cs index df0e7e6da..1118e61e2 100644 --- a/Shoko.Server/Repositories/Cached/AuthTokensRepository.cs +++ b/Shoko.Server/Repositories/Cached/AuthTokensRepository.cs @@ -19,23 +19,41 @@ private AuthTokensRepository() public AuthTokens GetByToken(string token) { - if (string.IsNullOrEmpty(token)) return null; - var tokens = Tokens.GetMultiple(token).ToList(); - var auth = tokens.FirstOrDefault(); - if (tokens.Count <= 1) return auth; - tokens.Remove(auth); - tokens.ForEach(Delete); - return auth; + lock (Cache) + { + if (string.IsNullOrEmpty(token)) return null; + var tokens = Tokens.GetMultiple(token).ToList(); + var auth = tokens.FirstOrDefault(); + if (tokens.Count <= 1) return auth; + tokens.Remove(auth); + tokens.ForEach(Delete); + return auth; + } } - public void DeleteAllWithUserID(int id) => UserIDs.GetMultiple(id).ToList().ForEach(Delete); + public void DeleteAllWithUserID(int id) + { + lock (Cache) + { + UserIDs.GetMultiple(id).ToList().ForEach(Delete); + } + } public void DeleteWithToken(string token) { - if (!string.IsNullOrEmpty(token)) Tokens.GetMultiple(token).ToList().ForEach(Delete); + lock (Cache) + { + if (!string.IsNullOrEmpty(token)) Tokens.GetMultiple(token).ToList().ForEach(Delete); + } } - public List GetByUserID(int userID) => UserIDs.GetMultiple(userID).ToList(); + public List GetByUserID(int userID) + { + lock (Cache) + { + return UserIDs.GetMultiple(userID).ToList(); + } + } protected override int SelectKey(AuthTokens entity) => entity.AuthID; @@ -55,26 +73,30 @@ public string ValidateUser(string username, string password, string device) if (userrecord == null) return string.Empty; - int uid = userrecord.JMMUserID; - var tokens = UserIDs - .GetMultiple(uid).ToList(); - var auth = tokens.FirstOrDefault(a => - a.DeviceName.Equals(device, StringComparison.InvariantCultureIgnoreCase) && - !string.IsNullOrEmpty(a.Token)); - if (tokens.Count > 1) + lock (Cache) { - tokens.Remove(auth); - tokens.ForEach(Delete); + int uid = userrecord.JMMUserID; + var tokens = UserIDs + .GetMultiple(uid).ToList(); + var auth = tokens.FirstOrDefault(a => + a.DeviceName.Equals(device, StringComparison.InvariantCultureIgnoreCase) && + !string.IsNullOrEmpty(a.Token)); + if (tokens.Count > 1) + { + tokens.Remove(auth); + tokens.ForEach(Delete); + } + var apiKey = auth?.Token ?? string.Empty; + + if (!string.IsNullOrEmpty(apiKey)) return apiKey; + + apiKey = Guid.NewGuid().ToString(); + AuthTokens token = + new AuthTokens {UserID = uid, DeviceName = device.ToLowerInvariant(), Token = apiKey}; + Save(token); + + return apiKey; } - var apiKey = auth?.Token ?? string.Empty; - - if (!string.IsNullOrEmpty(apiKey)) return apiKey; - - apiKey = Guid.NewGuid().ToString(); - AuthTokens token = new AuthTokens { UserID = uid, DeviceName = device.ToLowerInvariant(), Token = apiKey }; - RepoFactory.AuthTokens.Save(token); - - return apiKey; } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Cached/CrossRef_AniDB_TvDBV2Repository.cs b/Shoko.Server/Repositories/Cached/CrossRef_AniDB_TvDBV2Repository.cs index 153f9909c..24b0e35f6 100644 --- a/Shoko.Server/Repositories/Cached/CrossRef_AniDB_TvDBV2Repository.cs +++ b/Shoko.Server/Repositories/Cached/CrossRef_AniDB_TvDBV2Repository.cs @@ -29,14 +29,20 @@ public static CrossRef_AniDB_TvDBV2Repository Create() public List GetByAnimeID(int id) { - return AnimeIDs.GetMultiple(id).OrderBy(xref => xref.AniDBStartEpisodeType) - .ThenBy(xref => xref.AniDBStartEpisodeNumber).ToList(); + lock (Cache) + { + return AnimeIDs.GetMultiple(id).OrderBy(xref => xref.AniDBStartEpisodeType) + .ThenBy(xref => xref.AniDBStartEpisodeNumber).ToList(); + } } public List GetByTvDBID(int id) { - return TvDBIDs.GetMultiple(id).OrderBy(xref => xref.AniDBStartEpisodeType) - .ThenBy(xref => xref.AniDBStartEpisodeNumber).ToList(); + lock (Cache) + { + return TvDBIDs.GetMultiple(id).OrderBy(xref => xref.AniDBStartEpisodeType) + .ThenBy(xref => xref.AniDBStartEpisodeNumber).ToList(); + } } public ILookup GetByAnimeIDs(IReadOnlyCollection animeIds) @@ -49,27 +55,36 @@ public ILookup GetByAnimeIDs(IReadOnlyCollection.Instance; } - return animeIds.SelectMany(id => AnimeIDs.GetMultiple(id)) - .OrderBy(xref => xref.AniDBStartEpisodeType).ThenBy(xref => xref.AniDBStartEpisodeNumber) - .ToLookup(xref => xref.AnimeID); + lock (Cache) + { + return animeIds.SelectMany(id => AnimeIDs.GetMultiple(id)) + .OrderBy(xref => xref.AniDBStartEpisodeType).ThenBy(xref => xref.AniDBStartEpisodeNumber) + .ToLookup(xref => xref.AnimeID); + } } public List GetByAnimeIDEpTypeEpNumber(int id, int aniEpType, int aniEpisodeNumber) { - return AnimeIDs.GetMultiple(id) - .Where(xref => xref.AniDBStartEpisodeType == aniEpType && - xref.AniDBStartEpisodeNumber <= aniEpisodeNumber) - .OrderByDescending(xref => xref.AniDBStartEpisodeNumber).ToList(); + lock (Cache) + { + return AnimeIDs.GetMultiple(id) + .Where(xref => xref.AniDBStartEpisodeType == aniEpType && + xref.AniDBStartEpisodeNumber <= aniEpisodeNumber) + .OrderByDescending(xref => xref.AniDBStartEpisodeNumber).ToList(); + } } public CrossRef_AniDB_TvDBV2 GetByTvDBID(int id, int season, int episodeNumber, int animeID, int aniEpType, int aniEpisodeNumber) { - return TvDBIDs.GetMultiple(id).FirstOrDefault(xref => xref.TvDBSeasonNumber == season && - xref.TvDBStartEpisodeNumber == episodeNumber && - xref.AnimeID == animeID && - xref.AniDBStartEpisodeType == aniEpType && - xref.AniDBStartEpisodeNumber == aniEpisodeNumber); + lock (Cache) + { + return TvDBIDs.GetMultiple(id).FirstOrDefault(xref => xref.TvDBSeasonNumber == season && + xref.TvDBStartEpisodeNumber == episodeNumber && + xref.AnimeID == animeID && + xref.AniDBStartEpisodeType == aniEpType && + xref.AniDBStartEpisodeNumber == aniEpisodeNumber); + } } public override void RegenerateDb() diff --git a/Shoko.Server/Repositories/Cached/CrossRef_AniDB_TvDB_EpisodeRepository.cs b/Shoko.Server/Repositories/Cached/CrossRef_AniDB_TvDB_EpisodeRepository.cs index 12bfd0640..ba168785a 100644 --- a/Shoko.Server/Repositories/Cached/CrossRef_AniDB_TvDB_EpisodeRepository.cs +++ b/Shoko.Server/Repositories/Cached/CrossRef_AniDB_TvDB_EpisodeRepository.cs @@ -27,12 +27,18 @@ public static CrossRef_AniDB_TvDB_EpisodeRepository Create() public CrossRef_AniDB_TvDB_Episode GetByAniDBEpisodeID(int id) { // TODO Change this when multiple AniDB <=> TvDB Episode mappings - return EpisodeIDs.GetOne(id); + lock (Cache) + { + return EpisodeIDs.GetOne(id); + } } public List GetByAnimeID(int id) { - return AnimeIDs.GetMultiple(id); + lock (Cache) + { + return AnimeIDs.GetMultiple(id); + } } public override void RegenerateDb() diff --git a/Shoko.Server/Repositories/Cached/CrossRef_CustomTagRepository.cs b/Shoko.Server/Repositories/Cached/CrossRef_CustomTagRepository.cs index f37881ee2..b62fabc4b 100644 --- a/Shoko.Server/Repositories/Cached/CrossRef_CustomTagRepository.cs +++ b/Shoko.Server/Repositories/Cached/CrossRef_CustomTagRepository.cs @@ -43,47 +43,28 @@ public static CrossRef_CustomTagRepository Create() public List GetByAnimeID(int id) { - return Refs.GetMultiple(id, (int) CustomTagCrossRefType.Anime); - - /* - var tags = session - .CreateCriteria(typeof(CrossRef_CustomTag)) - .Add(Restrictions.Eq("CrossRefID", id)) - .Add(Restrictions.Eq("CrossRefType", (int) CustomTagCrossRefType.Anime)) - .List(); - - return new List(tags);*/ + lock (Cache) + { + return Refs.GetMultiple(id, (int) CustomTagCrossRefType.Anime); + } } public List GetByCustomTagID(int id) { - return Tags.GetMultiple(id); - /* - var tags = session - .CreateCriteria(typeof(CrossRef_CustomTag)) - .Add(Restrictions.Eq("CustomTagID", id)) - .List(); - - return new List(tags);*/ + lock (Cache) + { + return Tags.GetMultiple(id); + } } public List GetByUniqueID(int customTagID, int crossRefType, int crossRefID) { - return Refs.GetMultiple(crossRefID, crossRefType).Where(a => a.CustomTagID == customTagID).ToList(); - /* - using (var session = JMMService.SessionFactory.OpenSession()) + lock (Cache) { - var tags = session - .CreateCriteria(typeof(CrossRef_CustomTag)) - .Add(Restrictions.Eq("CustomTagID", customTagID)) - .Add(Restrictions.Eq("CrossRefType", crossRefType)) - .Add(Restrictions.Eq("CrossRefID", crossRefID)) - .List(); - - return new List(tags); - }*/ + return Refs.GetMultiple(crossRefID, crossRefType).Where(a => a.CustomTagID == customTagID).ToList(); + } } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Cached/CrossRef_File_EpisodeRepository.cs b/Shoko.Server/Repositories/Cached/CrossRef_File_EpisodeRepository.cs index 63f895d99..2b45295db 100644 --- a/Shoko.Server/Repositories/Cached/CrossRef_File_EpisodeRepository.cs +++ b/Shoko.Server/Repositories/Cached/CrossRef_File_EpisodeRepository.cs @@ -62,19 +62,28 @@ protected override int SelectKey(CrossRef_File_Episode entity) public List GetByHash(string hash) { - return Hashes.GetMultiple(hash).OrderBy(a => a.EpisodeOrder).ToList(); + lock (Cache) + { + return Hashes.GetMultiple(hash).OrderBy(a => a.EpisodeOrder).ToList(); + } } public List GetByAnimeID(int animeID) { - return Animes.GetMultiple(animeID); + lock (Cache) + { + return Animes.GetMultiple(animeID); + } } public List GetByFileNameAndSize(string filename, long filesize) { - return Filenames.GetMultiple(filename).Where(a => a.FileSize == filesize).ToList(); + lock (Cache) + { + return Filenames.GetMultiple(filename).Where(a => a.FileSize == filesize).ToList(); + } } /// @@ -85,12 +94,18 @@ public List GetByFileNameAndSize(string filename, long fi /// public CrossRef_File_Episode GetByHashAndEpisodeID(string hash, int episodeID) { - return Hashes.GetMultiple(hash).FirstOrDefault(a => a.EpisodeID == episodeID); + lock (Cache) + { + return Hashes.GetMultiple(hash).FirstOrDefault(a => a.EpisodeID == episodeID); + } } public List GetByEpisodeID(int episodeID) { - return Episodes.GetMultiple(episodeID); + lock (Cache) + { + return Episodes.GetMultiple(episodeID); + } } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Cached/CustomTagRepository.cs b/Shoko.Server/Repositories/Cached/CustomTagRepository.cs index 9c0e4f218..14056e4d2 100644 --- a/Shoko.Server/Repositories/Cached/CustomTagRepository.cs +++ b/Shoko.Server/Repositories/Cached/CustomTagRepository.cs @@ -45,15 +45,6 @@ public List GetByAnimeID(int animeID) .Select(a => GetByID(a.CustomTagID)) .Where(a => a != null) .ToList(); - /* - var tags = - session.CreateQuery( - "Select tag FROM CustomTag as tag, CrossRef_CustomTag as xref WHERE tag.CustomTagID = xref.CustomTagID AND xref.CrossRefID= :animeID AND xref.CrossRefType= :xrefType") - .SetParameter("animeID", animeID) - .SetParameter("xrefType", (int) CustomTagCrossRefType.Anime) - .List(); - - return new List(tags);*/ } @@ -64,24 +55,6 @@ public Dictionary> GetByAnimeIDs(ISessionWrapper session, i .Select(b => GetByID(b.CustomTagID)) .Where(b => b != null) .ToList()); - /* - throw new ArgumentNullException(nameof(session)); - if (animeIDs == null) - throw new ArgumentNullException(nameof(animeIDs)); - - if (animeIDs.Length == 0) - { - return EmptyLookup.Instance; - } - - var tags = session.CreateQuery( - "Select xref.CrossRefID, tag FROM CustomTag as tag, CrossRef_CustomTag as xref WHERE tag.CustomTagID = xref.CustomTagID AND xref.CrossRefID IN (:animeIDs) AND xref.CrossRefType= :xrefType") - .SetParameterList("animeIDs", animeIDs) - .SetParameter("xrefType", (int)CustomTagCrossRefType.Anime) - .List() - .ToLookup(t => (int)t[0], t => (CustomTag)t[1]); - - return tags;*/ } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Cached/GroupFilterRepository.cs b/Shoko.Server/Repositories/Cached/GroupFilterRepository.cs index 004d25f69..eb036f0ac 100644 --- a/Shoko.Server/Repositories/Cached/GroupFilterRepository.cs +++ b/Shoko.Server/Repositories/Cached/GroupFilterRepository.cs @@ -33,13 +33,25 @@ private GroupFilterRepository() { EndSaveCallback = (obj) => { - Types[obj.GroupFilterID] = obj.Types; - Changes.AddOrUpdate(obj.GroupFilterID); + lock (Types) + { + lock (Changes) + { + Types[obj.GroupFilterID] = obj.Types; + Changes.AddOrUpdate(obj.GroupFilterID); + } + } }; EndDeleteCallback = (obj) => { - Types.Remove(obj.GroupFilterID); - Changes.Remove(obj.GroupFilterID); + lock (Types) + { + lock (Changes) + { + Types.Remove(obj.GroupFilterID); + Changes.Remove(obj.GroupFilterID); + } + } }; } @@ -66,18 +78,14 @@ public override void PopulateIndexes() public override void RegenerateDb() { foreach (SVR_GroupFilter g in Cache.Values.ToList()) - { if (g.GroupFilterID != 0 && g.GroupsIdsVersion < SVR_GroupFilter.GROUPFILTER_VERSION || g.GroupConditionsVersion < SVR_GroupFilter.GROUPCONDITIONS_VERSION) { if (g.GroupConditionsVersion == 0) - { g.Conditions = RepoFactory.GroupFilterCondition.GetByGroupFilterID(g.GroupFilterID); - } Save(g, true); PostProcessFilters.Add(g); } - } } @@ -87,15 +95,11 @@ public override void PostProcess() ServerState.Instance.CurrentSetupStatus = string.Format(Commons.Properties.Resources.Database_Cache, t, string.Empty); foreach (SVR_GroupFilter g in Cache.Values.ToList()) - { if (g.GroupsIdsVersion < SVR_GroupFilter.GROUPFILTER_VERSION || g.GroupConditionsVersion < SVR_GroupFilter.GROUPCONDITIONS_VERSION || g.SeriesIdsVersion < SVR_GroupFilter.SERIEFILTER_VERSION) - { if (!PostProcessFilters.Contains(g)) PostProcessFilters.Add(g); - } - } int max = PostProcessFilters.Count; int cnt = 0; foreach (SVR_GroupFilter gf in PostProcessFilters) @@ -109,9 +113,7 @@ public override void PostProcess() gf.GroupConditionsVersion < SVR_GroupFilter.GROUPCONDITIONS_VERSION || gf.SeriesIdsVersion < SVR_GroupFilter.SERIEFILTER_VERSION || gf.GroupConditionsVersion < SVR_GroupFilter.GROUPCONDITIONS_VERSION) - { gf.CalculateGroupsAndSeries(); - } Save(gf); } @@ -121,15 +123,12 @@ public override void PostProcess() " " + Commons.Properties.Resources.GroupFilter_Cleanup); IReadOnlyList all = GetAll(); HashSet set = new HashSet(all); - List notin = all.Except(set)?.ToList(); + List notin = all.Except(set).ToList(); Delete(notin); // Remove orphaned group filter conditions - List toremove = new List(); - foreach (GroupFilterCondition condition in RepoFactory.GroupFilterCondition.GetAll().ToList()) - { - if (RepoFactory.GroupFilter.GetByID(condition.GroupFilterID) == null) toremove.Add(condition); - } + List toremove = RepoFactory.GroupFilterCondition.GetAll().ToList() + .Where(condition => RepoFactory.GroupFilter.GetByID(condition.GroupFilterID) == null).ToList(); RepoFactory.GroupFilterCondition.Delete(toremove); CleanUpEmptyDirectoryFilters(); @@ -137,23 +136,15 @@ public override void PostProcess() PostProcessFilters = null; } - - //TODO Cleanup function for Empty Tags and Empty Years public void CleanUpEmptyDirectoryFilters() { - List toremove = new List(); - foreach (SVR_GroupFilter gf in GetAll()) - { - if (gf.GroupsIds.Count == 0 && string.IsNullOrEmpty(gf.GroupsIdsString) && gf.SeriesIds.Count == 0 && - string.IsNullOrEmpty(gf.SeriesIdsString)) - { - toremove.Add(gf); - } - } + List toremove = GetAll() + .Where(a => (a.FilterType & (int) GroupFilterType.Directory) == (int) GroupFilterType.Directory).Where( + gf => gf.GroupsIds.Count == 0 && string.IsNullOrEmpty(gf.GroupsIdsString) && + gf.SeriesIds.Count == 0 && string.IsNullOrEmpty(gf.SeriesIdsString)).ToList(); Delete(toremove); } - public void CreateOrVerifyLockedFilters() { using (var session = DatabaseFactory.SessionFactory.OpenSession()) @@ -290,202 +281,189 @@ public void CreateOrVerifyLockedFilters() public void CreateOrVerifyDirectoryFilters(bool frominit = false, HashSet tags = null, HashSet airdate = null, SortedSet season = null) { - using (var session = DatabaseFactory.SessionFactory.OpenSession()) - { - string t = "GroupFilter"; + string t = "GroupFilter"; - List lockedGFs = GetLockedGroupFilters(); + List lockedGFs = GetLockedGroupFilters(); - SVR_GroupFilter tagsdirec = lockedGFs.FirstOrDefault( - a => a.FilterType == (int) (GroupFilterType.Directory | GroupFilterType.Tag)); - if (tagsdirec != null) + SVR_GroupFilter tagsdirec = lockedGFs.FirstOrDefault( + a => a.FilterType == (int) (GroupFilterType.Directory | GroupFilterType.Tag)); + if (tagsdirec != null) + { + HashSet alltags; + if (tags == null) + alltags = new HashSet( + RepoFactory.AniDB_Tag.GetAll() + .Select(a => a.TagName) + .Distinct(StringComparer.InvariantCultureIgnoreCase), + StringComparer.InvariantCultureIgnoreCase); + else + alltags = new HashSet(tags.Distinct(StringComparer.InvariantCultureIgnoreCase), + StringComparer.InvariantCultureIgnoreCase); + HashSet notin = + new HashSet( + lockedGFs.Where(a => a.FilterType == (int) GroupFilterType.Tag) + .Select(a => a.Conditions.FirstOrDefault()?.ConditionParameter), + StringComparer.InvariantCultureIgnoreCase); + alltags.ExceptWith(notin); + + int max = alltags.Count; + int cnt = 0; + //AniDB Tags are in english so we use en-us culture + TextInfo tinfo = new CultureInfo("en-US", false).TextInfo; + foreach (string s in alltags) { - HashSet alltags; - if (tags == null) - alltags = new HashSet( - RepoFactory.AniDB_Tag.GetAll() - .Select(a => a.TagName) - .Distinct(StringComparer.InvariantCultureIgnoreCase), - StringComparer.InvariantCultureIgnoreCase); - else - alltags = new HashSet(tags.Distinct(StringComparer.InvariantCultureIgnoreCase), - StringComparer.InvariantCultureIgnoreCase); - HashSet notin = - new HashSet( - lockedGFs.Where(a => a.FilterType == (int) GroupFilterType.Tag) - .Select(a => a.Conditions.FirstOrDefault()?.ConditionParameter), - StringComparer.InvariantCultureIgnoreCase); - alltags.ExceptWith(notin); - - int max = alltags.Count; - int cnt = 0; - //AniDB Tags are in english so we use en-us culture - TextInfo tinfo = new CultureInfo("en-US", false).TextInfo; - foreach (string s in alltags) + cnt++; + if (frominit) + ServerState.Instance.CurrentSetupStatus = string.Format( + Commons.Properties.Resources.Database_Cache, t, + Commons.Properties.Resources.Filter_CreatingTag + " " + + Commons.Properties.Resources.Filter_Filter + " " + cnt + "/" + max + " - " + s); + SVR_GroupFilter yf = new SVR_GroupFilter { - cnt++; - if (frominit) - ServerState.Instance.CurrentSetupStatus = string.Format( - Commons.Properties.Resources.Database_Cache, t, - Commons.Properties.Resources.Filter_CreatingTag + " " + - Commons.Properties.Resources.Filter_Filter + " " + cnt + "/" + max + " - " + s); - SVR_GroupFilter yf = new SVR_GroupFilter - { - ParentGroupFilterID = tagsdirec.GroupFilterID, - InvisibleInClients = 0, - GroupFilterName = tinfo.ToTitleCase(s.Replace("`", "'")), - BaseCondition = 1, - Locked = 1, - SortingCriteria = "5;1", - FilterType = (int) GroupFilterType.Tag - }; - GroupFilterCondition gfc = new GroupFilterCondition - { - ConditionType = (int)GroupFilterConditionType.Tag, - ConditionOperator = (int)GroupFilterOperator.In, - ConditionParameter = s, - GroupFilterID = yf.GroupFilterID - }; - yf.Conditions.Add(gfc); - yf.CalculateGroupsAndSeries(); - Save(yf); - } + ParentGroupFilterID = tagsdirec.GroupFilterID, + InvisibleInClients = 0, + GroupFilterName = tinfo.ToTitleCase(s.Replace("`", "'")), + BaseCondition = 1, + Locked = 1, + SortingCriteria = "5;1", + FilterType = (int) GroupFilterType.Tag + }; + GroupFilterCondition gfc = new GroupFilterCondition + { + ConditionType = (int)GroupFilterConditionType.Tag, + ConditionOperator = (int)GroupFilterOperator.In, + ConditionParameter = s, + GroupFilterID = yf.GroupFilterID + }; + yf.Conditions.Add(gfc); + yf.CalculateGroupsAndSeries(); + Save(yf); } - SVR_GroupFilter yearsdirec = lockedGFs.FirstOrDefault( - a => a.FilterType == (int) (GroupFilterType.Directory | GroupFilterType.Year)); - if (yearsdirec != null) + } + SVR_GroupFilter yearsdirec = lockedGFs.FirstOrDefault( + a => a.FilterType == (int) (GroupFilterType.Directory | GroupFilterType.Year)); + if (yearsdirec != null) + { + HashSet allyears; + if (airdate == null || airdate.Count == 0) { - HashSet allyears; - if (airdate == null || airdate.Count == 0) + List grps = + RepoFactory.AnimeSeries.GetAll().Select(a => a.Contract).Where(a => a != null).ToList(); + + allyears = new HashSet(StringComparer.Ordinal); + foreach (CL_AnimeSeries_User ser in grps) { - List grps = - RepoFactory.AnimeSeries.GetAll().Select(a => a.Contract).Where(a => a != null).ToList(); - - allyears = new HashSet(StringComparer.Ordinal); - foreach (CL_AnimeSeries_User ser in grps) - { - int endyear = ser.AniDBAnime.AniDBAnime.EndYear; - int startyear = ser.AniDBAnime.AniDBAnime.BeginYear; - if (endyear == 0) endyear = DateTime.Today.Year; - if (startyear != 0) - allyears.UnionWith(Enumerable.Range(startyear, - endyear - startyear + 1) - .Select(a => a.ToString())); - } + int endyear = ser.AniDBAnime.AniDBAnime.EndYear; + int startyear = ser.AniDBAnime.AniDBAnime.BeginYear; + if (endyear == 0) endyear = DateTime.Today.Year; + if (startyear != 0) + allyears.UnionWith(Enumerable.Range(startyear, + endyear - startyear + 1) + .Select(a => a.ToString())); } - else + } + else + { + allyears = new HashSet(airdate.Select(a => a.ToString()), StringComparer.Ordinal); + } + HashSet notin = + new HashSet( + lockedGFs.Where(a => a.FilterType == (int) GroupFilterType.Year) + .Select(a => a.Conditions.FirstOrDefault()?.ConditionParameter), + StringComparer.InvariantCultureIgnoreCase); + allyears.ExceptWith(notin); + int max = allyears.Count; + int cnt = 0; + foreach (string s in allyears) + { + cnt++; + if (frominit) + ServerState.Instance.CurrentSetupStatus = string.Format( + Commons.Properties.Resources.Database_Cache, t, + Commons.Properties.Resources.Filter_CreatingYear + " " + + Commons.Properties.Resources.Filter_Filter + " " + cnt + "/" + max + " - " + s); + SVR_GroupFilter yf = new SVR_GroupFilter { - allyears = new HashSet(airdate.Select(a => a.ToString()), StringComparer.Ordinal); - } - HashSet notin = - new HashSet( - lockedGFs.Where(a => a.FilterType == (int) GroupFilterType.Year) - .Select(a => a.Conditions.FirstOrDefault()?.ConditionParameter), - StringComparer.InvariantCultureIgnoreCase); - allyears.ExceptWith(notin); - int max = allyears.Count; - int cnt = 0; - foreach (string s in allyears) + ParentGroupFilterID = yearsdirec.GroupFilterID, + InvisibleInClients = 0, + GroupFilterName = s, + BaseCondition = 1, + Locked = 1, + SortingCriteria = "5;1", + FilterType = (int) GroupFilterType.Year, + ApplyToSeries = 1 + }; + GroupFilterCondition gfc = new GroupFilterCondition { - cnt++; - if (frominit) - ServerState.Instance.CurrentSetupStatus = string.Format( - Commons.Properties.Resources.Database_Cache, t, - Commons.Properties.Resources.Filter_CreatingYear + " " + - Commons.Properties.Resources.Filter_Filter + " " + cnt + "/" + max + " - " + s); - SVR_GroupFilter yf = new SVR_GroupFilter - { - ParentGroupFilterID = yearsdirec.GroupFilterID, - InvisibleInClients = 0, - GroupFilterName = s, - BaseCondition = 1, - Locked = 1, - SortingCriteria = "5;1", - FilterType = (int) GroupFilterType.Year, - ApplyToSeries = 1 - }; - GroupFilterCondition gfc = new GroupFilterCondition - { - ConditionType = (int)GroupFilterConditionType.Year, - ConditionOperator = (int)GroupFilterOperator.Include, - ConditionParameter = s, - GroupFilterID = yf.GroupFilterID - }; - yf.Conditions.Add(gfc); - yf.CalculateGroupsAndSeries(); - Save(yf); - } + ConditionType = (int)GroupFilterConditionType.Year, + ConditionOperator = (int)GroupFilterOperator.Include, + ConditionParameter = s, + GroupFilterID = yf.GroupFilterID + }; + yf.Conditions.Add(gfc); + yf.CalculateGroupsAndSeries(); + Save(yf); } - SVR_GroupFilter seasonsdirectory = lockedGFs.FirstOrDefault( - a => a.FilterType == (int) (GroupFilterType.Directory | GroupFilterType.Season)); - if (seasonsdirectory != null) + } + SVR_GroupFilter seasonsdirectory = lockedGFs.FirstOrDefault( + a => a.FilterType == (int) (GroupFilterType.Directory | GroupFilterType.Season)); + if (seasonsdirectory != null) + { + SortedSet allseasons; + if (season == null || season.Count == 0) { - SortedSet allseasons; - if (season == null || season.Count == 0) - { - List grps = - RepoFactory.AnimeSeries.GetAll().Select(a => a.Contract).Where(a => a != null).ToList(); - - allseasons = new SortedSet(new SeasonComparator()); - foreach (CL_AnimeSeries_User ser in grps) - { - allseasons.UnionWith(ser.AniDBAnime.Stat_AllSeasons); - } - } - else + List grps = + RepoFactory.AnimeSeries.GetAll().Select(a => a.Contract).Where(a => a != null).ToList(); + + allseasons = new SortedSet(new SeasonComparator()); + foreach (CL_AnimeSeries_User ser in grps) + allseasons.UnionWith(ser.AniDBAnime.Stat_AllSeasons); + } + else + { + allseasons = season; + } + HashSet notin = + new HashSet( + lockedGFs.Where(a => a.FilterType == (int) GroupFilterType.Season) + .Select(a => a.Conditions.FirstOrDefault()?.ConditionParameter), + StringComparer.InvariantCultureIgnoreCase); + allseasons.ExceptWith(notin); + int max = allseasons.Count; + int cnt = 0; + foreach (string s in allseasons) + { + cnt++; + if (frominit) + ServerState.Instance.CurrentSetupStatus = string.Format( + Commons.Properties.Resources.Database_Cache, t, + Commons.Properties.Resources.Filter_CreatingSeason + " " + + Commons.Properties.Resources.Filter_Filter + " " + cnt + "/" + max + " - " + s); + SVR_GroupFilter yf = new SVR_GroupFilter { - allseasons = season; - } - HashSet notin = - new HashSet( - lockedGFs.Where(a => a.FilterType == (int) GroupFilterType.Season) - .Select(a => a.Conditions.FirstOrDefault()?.ConditionParameter), - StringComparer.InvariantCultureIgnoreCase); - allseasons.ExceptWith(notin); - int max = allseasons.Count; - int cnt = 0; - foreach (string s in allseasons) + ParentGroupFilterID = seasonsdirectory.GroupFilterID, + InvisibleInClients = 0, + GroupFilterName = s, + BaseCondition = 1, + Locked = 1, + SortingCriteria = "5;1", + FilterType = (int) GroupFilterType.Season, + ApplyToSeries = 1 + }; + GroupFilterCondition gfc = new GroupFilterCondition { - cnt++; - if (frominit) - ServerState.Instance.CurrentSetupStatus = string.Format( - Commons.Properties.Resources.Database_Cache, t, - Commons.Properties.Resources.Filter_CreatingSeason + " " + - Commons.Properties.Resources.Filter_Filter + " " + cnt + "/" + max + " - " + s); - SVR_GroupFilter yf = new SVR_GroupFilter - { - ParentGroupFilterID = seasonsdirectory.GroupFilterID, - InvisibleInClients = 0, - GroupFilterName = s, - BaseCondition = 1, - Locked = 1, - SortingCriteria = "5;1", - FilterType = (int) GroupFilterType.Season, - ApplyToSeries = 1 - }; - GroupFilterCondition gfc = new GroupFilterCondition - { - ConditionType = (int)GroupFilterConditionType.Season, - ConditionOperator = (int)GroupFilterOperator.In, - ConditionParameter = s, - GroupFilterID = yf.GroupFilterID - }; - yf.Conditions.Add(gfc); - yf.CalculateGroupsAndSeries(); - Save(yf); - } + ConditionType = (int)GroupFilterConditionType.Season, + ConditionOperator = (int)GroupFilterOperator.In, + ConditionParameter = s, + GroupFilterID = yf.GroupFilterID + }; + yf.Conditions.Add(gfc); + yf.CalculateGroupsAndSeries(); + Save(yf); } - CleanUpEmptyDirectoryFilters(); } - } - - //Disable base saves. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("...", false)] - public override void Save(IReadOnlyCollection objs) - { - throw new NotSupportedException(); + CleanUpEmptyDirectoryFilters(); } public override void Save(SVR_GroupFilter obj) @@ -498,9 +476,7 @@ public void Save(SVR_GroupFilter obj, bool onlyconditions) lock (obj) { if (!onlyconditions) - { obj.UpdateEntityReferenceStrings(); - } bool resaveConditions = obj.GroupFilterID == 0; obj.GroupConditions = Newtonsoft.Json.JsonConvert.SerializeObject(obj._conditions); obj.GroupConditionsVersion = SVR_GroupFilter.GROUPCONDITIONS_VERSION; @@ -529,20 +505,30 @@ public void BatchUpdate(ISessionWrapper session, IEnumerable gr if (groupFilters == null) throw new ArgumentNullException(nameof(groupFilters)); - foreach (SVR_GroupFilter groupFilter in groupFilters) + lock (globalDBLock) { - session.Update(groupFilter); + foreach (SVR_GroupFilter groupFilter in groupFilters) + lock (groupFilter) + { + session.Update(groupFilter); + } } } public List GetByParentID(int parentid) { - return Parents.GetMultiple(parentid); + lock (Cache) + { + return Parents.GetMultiple(parentid); + } } public List GetTopLevel() { - return Parents.GetMultiple(0); + lock (Cache) + { + return Parents.GetMultiple(0); + } } /// @@ -556,7 +542,9 @@ public ILookup CalculateAnimeGroupsPerTagGroupFilter(ISessionWrapper s if (session == null) throw new ArgumentNullException(nameof(session)); - var groupsByFilter = session.CreateSQLQuery(@" + lock (globalDBLock) + { + var groupsByFilter = session.CreateSQLQuery(@" SELECT DISTINCT grpFilter.GroupFilterID, grp.AnimeGroupID FROM AnimeGroup grp INNER JOIN AnimeSeries series @@ -569,45 +557,55 @@ INNER JOIN GroupFilter grpFilter ON grpFilter.GroupFilterName = tag.TagName AND grpFilter.FilterType = :tagType ORDER BY grpFilter.GroupFilterID, grp.AnimeGroupID") - .AddScalar("GroupFilterID", NHibernateUtil.Int32) - .AddScalar("AnimeGroupID", NHibernateUtil.Int32) - .SetInt32("tagType", (int) GroupFilterType.Tag) - .List() - .ToLookup(r => (int) r[0], r => (int) r[1]); + .AddScalar("GroupFilterID", NHibernateUtil.Int32) + .AddScalar("AnimeGroupID", NHibernateUtil.Int32) + .SetInt32("tagType", (int) GroupFilterType.Tag) + .List() + .ToLookup(r => (int) r[0], r => (int) r[1]); - return groupsByFilter; + return groupsByFilter; + } } public List GetLockedGroupFilters() { - return Cache.Values.Where(a => a.Locked == 1).ToList(); + lock (Cache) + { + return Cache.Values.Where(a => a.Locked == 1).ToList(); + } } public List GetWithConditionTypesAndAll(HashSet types) { - HashSet filters = new HashSet(Cache.Values.Where(a => a.FilterType == (int) GroupFilterType.All) - .Select(a => a.GroupFilterID)); - foreach (GroupFilterConditionType t in types) + lock (Cache) { - filters.UnionWith(Types.FindInverse(t)); - } + HashSet filters = new HashSet(Cache.Values + .Where(a => a.FilterType == (int) GroupFilterType.All) + .Select(a => a.GroupFilterID)); + foreach (GroupFilterConditionType t in types) + filters.UnionWith(Types.FindInverse(t)); - return filters.Select(a => Cache.Get(a)).ToList(); + return filters.Select(a => Cache.Get(a)).ToList(); + } } public List GetWithConditionsTypes(HashSet types) { - HashSet filters = new HashSet(); - foreach (GroupFilterConditionType t in types) + lock (Cache) { - filters.UnionWith(Types.FindInverse(t)); + HashSet filters = new HashSet(); + foreach (GroupFilterConditionType t in types) + filters.UnionWith(Types.FindInverse(t)); + return filters.Select(a => Cache.Get(a)).ToList(); } - return filters.Select(a => Cache.Get(a)).ToList(); } public ChangeTracker GetChangeTracker() { - return Changes; + lock (Changes) + { + return Changes; + } } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Cached/ImportFolderRepository.cs b/Shoko.Server/Repositories/Cached/ImportFolderRepository.cs index 540f53530..37e42acef 100644 --- a/Shoko.Server/Repositories/Cached/ImportFolderRepository.cs +++ b/Shoko.Server/Repositories/Cached/ImportFolderRepository.cs @@ -34,18 +34,25 @@ public static ImportFolderRepository Create() public SVR_ImportFolder GetByImportLocation(string importloc) { - return Cache.Values.FirstOrDefault(a => - a.ImportFolderLocation?.Replace('\\', Path.DirectorySeparatorChar) - .Replace('/', Path.DirectorySeparatorChar).TrimEnd(Path.DirectorySeparatorChar) - .Equals( - importloc?.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar) - .TrimEnd(Path.DirectorySeparatorChar), - StringComparison.InvariantCultureIgnoreCase) ?? false); + lock (Cache) + { + return Cache.Values.FirstOrDefault(a => + a.ImportFolderLocation?.Replace('\\', Path.DirectorySeparatorChar) + .Replace('/', Path.DirectorySeparatorChar).TrimEnd(Path.DirectorySeparatorChar) + .Equals( + importloc?.Replace('\\', Path.DirectorySeparatorChar) + .Replace('/', Path.DirectorySeparatorChar) + .TrimEnd(Path.DirectorySeparatorChar), + StringComparison.InvariantCultureIgnoreCase) ?? false); + } } public List GetByCloudId(int cloudid) { - return Cache.Values.Where(a => a.CloudID.HasValue && a.CloudID.Value == cloudid).ToList(); + lock (Cache) + { + return Cache.Values.Where(a => a.CloudID.HasValue && a.CloudID.Value == cloudid).ToList(); + } } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Cached/JMMUserRepository.cs b/Shoko.Server/Repositories/Cached/JMMUserRepository.cs index 865b8b53e..1d58518ac 100644 --- a/Shoko.Server/Repositories/Cached/JMMUserRepository.cs +++ b/Shoko.Server/Repositories/Cached/JMMUserRepository.cs @@ -31,20 +31,9 @@ public override void RegenerateDb() { } - - //Disable base saves. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("...", false)] public override void Save(SVR_JMMUser obj) { - throw new NotSupportedException(); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("...", false)] - public override void Save(IReadOnlyCollection objs) - { - throw new NotSupportedException(); + Save(obj, true); } public void Save(SVR_JMMUser obj, bool updateGroupFilters) @@ -75,23 +64,50 @@ public void Save(SVR_JMMUser obj, bool updateGroupFilters) } - public SVR_JMMUser GetByUsername(string username) => Cache.Values.FirstOrDefault(x => - x.Username.Equals(username, StringComparison.InvariantCultureIgnoreCase)); + public SVR_JMMUser GetByUsername(string username) + { + lock (Cache) + { + return Cache.Values.FirstOrDefault(x => + x.Username.Equals(username, StringComparison.InvariantCultureIgnoreCase)); + } + } - public List GetAniDBUsers() => Cache.Values.Where(a => a.IsAniDBUser == 1).ToList(); + public List GetAniDBUsers() + { + lock (Cache) + { + return Cache.Values.Where(a => a.IsAniDBUser == 1).ToList(); + } + } - public List GetTraktUsers() => Cache.Values.Where(a => a.IsTraktUser == 1).ToList(); + public List GetTraktUsers() + { + lock (Cache) + { + return Cache.Values.Where(a => a.IsTraktUser == 1).ToList(); + } + } public SVR_JMMUser AuthenticateUser(string userName, string password) { if (password == null) password = string.Empty; string hashedPassword = Digest.Hash(password); - return Cache.Values.FirstOrDefault(a => - a.Username.Equals(userName, StringComparison.InvariantCultureIgnoreCase) && - a.Password.Equals(hashedPassword)); + lock (Cache) + { + return Cache.Values.FirstOrDefault(a => + a.Username.Equals(userName, StringComparison.InvariantCultureIgnoreCase) && + a.Password.Equals(hashedPassword)); + } } - public long GetTotalRecordCount() => Cache.Keys.Count; + public long GetTotalRecordCount() + { + lock (Cache) + { + return Cache.Keys.Count; + } + } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Cached/TvDB_EpisodeRepository.cs b/Shoko.Server/Repositories/Cached/TvDB_EpisodeRepository.cs index 02d2cbe89..9b4b3f35e 100644 --- a/Shoko.Server/Repositories/Cached/TvDB_EpisodeRepository.cs +++ b/Shoko.Server/Repositories/Cached/TvDB_EpisodeRepository.cs @@ -31,12 +31,18 @@ public static TvDB_EpisodeRepository Create() public TvDB_Episode GetByTvDBID(int id) { - return EpisodeIDs.GetOne(id); + lock (Cache) + { + return EpisodeIDs.GetOne(id); + } } public List GetBySeriesID(int seriesID) { - return SeriesIDs.GetMultiple(seriesID); + lock (Cache) + { + return SeriesIDs.GetMultiple(seriesID); + } } /// @@ -46,7 +52,10 @@ public List GetBySeriesID(int seriesID) /// distinct list of integers public List GetSeasonNumbersForSeries(int seriesID) { - return SeriesIDs.GetMultiple(seriesID).Select(xref => xref.SeasonNumber).Distinct().ToList(); + lock (Cache) + { + return SeriesIDs.GetMultiple(seriesID).Select(xref => xref.SeasonNumber).Distinct().ToList(); + } } /// @@ -56,8 +65,11 @@ public List GetSeasonNumbersForSeries(int seriesID) /// The last TvDB Season Number, or -1 if unable public int getLastSeasonForSeries(int seriesID) { - if (SeriesIDs.GetMultiple(seriesID).Count == 0) return -1; - return SeriesIDs.GetMultiple(seriesID).Max(xref => xref.SeasonNumber); + lock (Cache) + { + if (SeriesIDs.GetMultiple(seriesID).Count == 0) return -1; + return SeriesIDs.GetMultiple(seriesID).Max(xref => xref.SeasonNumber); + } } /// @@ -68,7 +80,10 @@ public int getLastSeasonForSeries(int seriesID) /// List of TvDB_Episodes public List GetBySeriesIDAndSeasonNumber(int seriesID, int seasonNumber) { - return SeriesIDs.GetMultiple(seriesID).Where(xref => xref.SeasonNumber == seasonNumber).ToList(); + lock (Cache) + { + return SeriesIDs.GetMultiple(seriesID).Where(xref => xref.SeasonNumber == seasonNumber).ToList(); + } } /// @@ -80,14 +95,20 @@ public List GetBySeriesIDAndSeasonNumber(int seriesID, int seasonN /// public TvDB_Episode GetBySeriesIDSeasonNumberAndEpisode(int seriesID, int seasonNumber, int epNumber) { - return SeriesIDs.GetMultiple(seriesID).FirstOrDefault(xref => xref.SeasonNumber == seasonNumber && - xref.EpisodeNumber == epNumber); + lock (Cache) + { + return SeriesIDs.GetMultiple(seriesID).FirstOrDefault(xref => xref.SeasonNumber == seasonNumber && + xref.EpisodeNumber == epNumber); + } } public TvDB_Episode GetBySeriesIDAndDate(int seriesID, DateTime date) { - return SeriesIDs.GetMultiple(seriesID) - .FirstOrDefault(a => a.AirDate != null && Math.Abs((a.AirDate.Value - date).TotalDays) < 1.5D); + lock (Cache) + { + return SeriesIDs.GetMultiple(seriesID) + .FirstOrDefault(a => a.AirDate != null && Math.Abs((a.AirDate.Value - date).TotalDays) < 1.5D); + } } /// @@ -98,7 +119,10 @@ public TvDB_Episode GetBySeriesIDAndDate(int seriesID, DateTime date) /// int public int GetNumberOfEpisodesForSeason(int seriesID, int seasonNumber) { - return SeriesIDs.GetMultiple(seriesID).Count(xref => xref.SeasonNumber == seasonNumber); + lock (Cache) + { + return SeriesIDs.GetMultiple(seriesID).Count(xref => xref.SeasonNumber == seasonNumber); + } } /// @@ -109,35 +133,11 @@ public int GetNumberOfEpisodesForSeason(int seriesID, int seasonNumber) /// public List GetBySeriesIDAndSeasonNumberSorted(int seriesID, int seasonNumber) { - return Cache.Values.Where(xref => xref.SeriesID == seriesID && xref.SeasonNumber == seasonNumber) - .OrderBy(xref => xref.EpisodeNumber).ToList(); - } - - public ILookup GetByAnimeIDs(ISessionWrapper session, - int[] animeIds) - { - if (session == null) - throw new ArgumentNullException(nameof(session)); - if (animeIds == null) - throw new ArgumentNullException(nameof(animeIds)); - - if (animeIds.Length == 0) + lock (Cache) { - return new List().ToLookup(a => a.Id, a => a); + return Cache.Values.Where(xref => xref.SeriesID == seriesID && xref.SeasonNumber == seasonNumber) + .OrderBy(xref => xref.EpisodeNumber).ToList(); } - - var tvDBEpisodeByAnime = session.CreateSQLQuery(@" - SELECT {tvxref.*}, {tvep.*} - FROM CrossRef_AniDB_TvDBV2 tvxref - INNER JOIN TvDB_Episode tvep - ON tvxref.TvDBID = tvep.SeriesID - WHERE tvxref.AnimeID IN (:animeIds)") - .AddEntity("tvxref", typeof(CrossRef_AniDB_TvDBV2)) - .AddEntity("tvep", typeof(TvDB_Episode)) - .SetParameterList("animeIds", animeIds) - .List().ToLookup(r => ((CrossRef_AniDB_TvDBV2)r[0]).AnimeID, r => (TvDB_Episode)r[1]); - - return tvDBEpisodeByAnime; } public override void RegenerateDb() diff --git a/Shoko.Server/Repositories/Cached/TvDB_ImageFanartRepository.cs b/Shoko.Server/Repositories/Cached/TvDB_ImageFanartRepository.cs index fe798387d..7dc68af89 100644 --- a/Shoko.Server/Repositories/Cached/TvDB_ImageFanartRepository.cs +++ b/Shoko.Server/Repositories/Cached/TvDB_ImageFanartRepository.cs @@ -31,12 +31,18 @@ public static TvDB_ImageFanartRepository Create() public TvDB_ImageFanart GetByTvDBID(int id) { - return TvDBIDs.GetOne(id); + lock (Cache) + { + return TvDBIDs.GetOne(id); + } } public List GetBySeriesID(int seriesID) { - return SeriesIDs.GetMultiple(seriesID); + lock (Cache) + { + return SeriesIDs.GetMultiple(seriesID); + } } public ILookup GetByAnimeIDs(ISessionWrapper session, int[] animeIds) @@ -51,19 +57,22 @@ public ILookup GetByAnimeIDs(ISessionWrapper session, int return EmptyLookup.Instance; } - var fanartByAnime = session.CreateSQLQuery(@" + lock (globalDBLock) + { + var fanartByAnime = session.CreateSQLQuery(@" SELECT DISTINCT crAdbTvTb.AnimeID, {tvdbFanart.*} FROM CrossRef_AniDB_TvDBV2 AS crAdbTvTb INNER JOIN TvDB_ImageFanart AS tvdbFanart ON tvdbFanart.SeriesID = crAdbTvTb.TvDBID WHERE crAdbTvTb.AnimeID IN (:animeIds)") - .AddScalar("AnimeID", NHibernateUtil.Int32) - .AddEntity("tvdbFanart", typeof(TvDB_ImageFanart)) - .SetParameterList("animeIds", animeIds) - .List() - .ToLookup(r => (int) r[0], r => (TvDB_ImageFanart) r[1]); + .AddScalar("AnimeID", NHibernateUtil.Int32) + .AddEntity("tvdbFanart", typeof(TvDB_ImageFanart)) + .SetParameterList("animeIds", animeIds) + .List() + .ToLookup(r => (int) r[0], r => (TvDB_ImageFanart) r[1]); - return fanartByAnime; + return fanartByAnime; + } } public override void RegenerateDb() diff --git a/Shoko.Server/Repositories/Cached/TvDB_ImagePosterRepository.cs b/Shoko.Server/Repositories/Cached/TvDB_ImagePosterRepository.cs index 4a7ccb0a4..df751c5c5 100644 --- a/Shoko.Server/Repositories/Cached/TvDB_ImagePosterRepository.cs +++ b/Shoko.Server/Repositories/Cached/TvDB_ImagePosterRepository.cs @@ -35,12 +35,18 @@ public static TvDB_ImagePosterRepository Create() public TvDB_ImagePoster GetByTvDBID(int id) { - return TvDBIDs.GetOne(id); + lock (Cache) + { + return TvDBIDs.GetOne(id); + } } public List GetBySeriesID(int seriesID) { - return SeriesIDs.GetMultiple(seriesID); + lock (Cache) + { + return SeriesIDs.GetMultiple(seriesID); + } } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Cached/TvDB_ImageWideBannerRepository.cs b/Shoko.Server/Repositories/Cached/TvDB_ImageWideBannerRepository.cs index 7d27b1e09..5a6a616ff 100644 --- a/Shoko.Server/Repositories/Cached/TvDB_ImageWideBannerRepository.cs +++ b/Shoko.Server/Repositories/Cached/TvDB_ImageWideBannerRepository.cs @@ -40,12 +40,18 @@ public static TvDB_ImageWideBannerRepository Create() public TvDB_ImageWideBanner GetByTvDBID(int id) { - return TvDBIDs.GetOne(id); + lock (Cache) + { + return TvDBIDs.GetOne(id); + } } public List GetBySeriesID(int seriesID) { - return SeriesIDs.GetMultiple(seriesID); + lock (Cache) + { + return SeriesIDs.GetMultiple(seriesID); + } } public ILookup GetByAnimeIDs(ISessionWrapper session, int[] animeIds) @@ -60,19 +66,22 @@ public ILookup GetByAnimeIDs(ISessionWrapper session, return EmptyLookup.Instance; } - var bannersByAnime = session.CreateSQLQuery(@" + lock (globalDBLock) + { + var bannersByAnime = session.CreateSQLQuery(@" SELECT DISTINCT crAdbTvTb.AnimeID, {tvdbBanner.*} FROM CrossRef_AniDB_TvDBV2 AS crAdbTvTb INNER JOIN TvDB_ImageWideBanner AS tvdbBanner ON tvdbBanner.SeriesID = crAdbTvTb.TvDBID WHERE crAdbTvTb.AnimeID IN (:animeIds)") - .AddScalar("AnimeID", NHibernateUtil.Int32) - .AddEntity("tvdbBanner", typeof(TvDB_ImageWideBanner)) - .SetParameterList("animeIds", animeIds) - .List() - .ToLookup(r => (int) r[0], r => (TvDB_ImageWideBanner) r[1]); + .AddScalar("AnimeID", NHibernateUtil.Int32) + .AddEntity("tvdbBanner", typeof(TvDB_ImageWideBanner)) + .SetParameterList("animeIds", animeIds) + .List() + .ToLookup(r => (int) r[0], r => (TvDB_ImageWideBanner) r[1]); - return bannersByAnime; + return bannersByAnime; + } } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Cached/TvDB_SeriesRepository.cs b/Shoko.Server/Repositories/Cached/TvDB_SeriesRepository.cs index 318b1ae4a..9897addd5 100644 --- a/Shoko.Server/Repositories/Cached/TvDB_SeriesRepository.cs +++ b/Shoko.Server/Repositories/Cached/TvDB_SeriesRepository.cs @@ -27,7 +27,10 @@ public static TvDB_SeriesRepository Create() public TvDB_Series GetByTvDBID(int id) { - return TvDBIDs.GetOne(id); + lock (Cache) + { + return TvDBIDs.GetOne(id); + } } public ILookup> GetByAnimeIDsV2(ISessionWrapper session, @@ -43,21 +46,24 @@ public ILookup> GetByAnimeIDsV2(I return EmptyLookup>.Instance; } - var tvDbSeriesByAnime = session.CreateSQLQuery(@" + lock (globalDBLock) + { + var tvDbSeriesByAnime = session.CreateSQLQuery(@" SELECT {cr.*}, {series.*} FROM CrossRef_AniDB_TvDBV2 cr INNER JOIN TvDB_Series series ON series.SeriesID = cr.TvDBID WHERE cr.AnimeID IN (:animeIds)") - .AddEntity("cr", typeof(CrossRef_AniDB_TvDBV2)) - .AddEntity("series", typeof(TvDB_Series)) - .SetParameterList("animeIds", animeIds) - .List() - .ToLookup(r => ((CrossRef_AniDB_TvDBV2) r[0]).AnimeID, - r => new Tuple((CrossRef_AniDB_TvDBV2) r[0], - (TvDB_Series) r[1])); + .AddEntity("cr", typeof(CrossRef_AniDB_TvDBV2)) + .AddEntity("series", typeof(TvDB_Series)) + .SetParameterList("animeIds", animeIds) + .List() + .ToLookup(r => ((CrossRef_AniDB_TvDBV2) r[0]).AnimeID, + r => new Tuple((CrossRef_AniDB_TvDBV2) r[0], + (TvDB_Series) r[1])); - return tvDbSeriesByAnime; + return tvDbSeriesByAnime; + } } public override void RegenerateDb() diff --git a/Shoko.Server/Repositories/Cached/VideoLocalRepository.cs b/Shoko.Server/Repositories/Cached/VideoLocalRepository.cs index 8c6633ddf..116b39490 100644 --- a/Shoko.Server/Repositories/Cached/VideoLocalRepository.cs +++ b/Shoko.Server/Repositories/Cached/VideoLocalRepository.cs @@ -9,6 +9,7 @@ using NHibernate; using NHibernate.Util; using NutzCode.InMemoryIndex; +using Pri.LongPath; using Shoko.Server.Databases; using Shoko.Server.Models; using Shoko.Server.Extensions; @@ -84,7 +85,7 @@ public override void RegenerateDb() //Fix possible paths in filename if (!string.IsNullOrEmpty(a.FileName)) { - int b = a.FileName.LastIndexOf("\\", StringComparison.Ordinal); + int b = a.FileName.LastIndexOf($"{Path.DirectorySeparatorChar}", StringComparison.Ordinal); if (b > 0) a.FileName = a.FileName.Substring(b + 1); } @@ -108,7 +109,7 @@ public override void RegenerateDb() max = list.Count; list.ForEach(a => { - int b = a.FileName.LastIndexOf("\\", StringComparison.Ordinal); + int b = a.FileName.LastIndexOf($"{Path.DirectorySeparatorChar}", StringComparison.Ordinal); a.FileName = a.FileName.Substring(b + 1); Save(a, false); count++; @@ -235,37 +236,11 @@ public override void Delete(SVR_VideoLocal obj) list.Where(a => a != null).ForEach(a => RepoFactory.AnimeEpisode.Save(a)); } - //Disable base saves. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("...", false)] - public override void Delete(IReadOnlyCollection objs) - { - throw new NotSupportedException(); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("...", false)] - public override void Delete(int id) - { - throw new NotSupportedException(); - } - - //Disable base saves. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("...", false)] public override void Save(SVR_VideoLocal obj) { - throw new NotSupportedException(); + Save(obj, true); } - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("...", false)] - public override void Save(IReadOnlyCollection objs) - { - throw new NotSupportedException(); - } - - public void Save(SVR_VideoLocal obj, bool updateEpisodes) { lock (obj) @@ -280,10 +255,7 @@ public void Save(SVR_VideoLocal obj, bool updateEpisodes) } if (updateEpisodes) { - foreach (SVR_AnimeEpisode ep in obj.GetAnimeEpisodes()) - { - RepoFactory.AnimeEpisode.Save(ep); - } + RepoFactory.AnimeEpisode.Save(obj.GetAnimeEpisodes()); } } diff --git a/Shoko.Server/Repositories/Cached/VideoLocal_PlaceRepository.cs b/Shoko.Server/Repositories/Cached/VideoLocal_PlaceRepository.cs index dcdedad85..d1c051704 100644 --- a/Shoko.Server/Repositories/Cached/VideoLocal_PlaceRepository.cs +++ b/Shoko.Server/Repositories/Cached/VideoLocal_PlaceRepository.cs @@ -41,17 +41,26 @@ public override void RegenerateDb() public List GetByImportFolder(int importFolderID) { - return ImportFolders.GetMultiple(importFolderID); + lock (Cache) + { + return ImportFolders.GetMultiple(importFolderID); + } } public SVR_VideoLocal_Place GetByFilePathAndShareID(string filePath, int nshareID) { - return Paths.GetMultiple(filePath).FirstOrDefault(a => a.ImportFolderID == nshareID); + lock (Cache) + { + return Paths.GetMultiple(filePath).FirstOrDefault(a => a.ImportFolderID == nshareID); + } } public List GetByFilePathAndImportFolderType(string filePath, int folderType) { - return Paths.GetMultiple(filePath).FindAll(a => a.ImportFolderType == folderType); + lock (Cache) + { + return Paths.GetMultiple(filePath).FindAll(a => a.ImportFolderType == folderType); + } } public override void Delete(SVR_VideoLocal_Place obj) @@ -67,22 +76,6 @@ public override void Delete(SVR_VideoLocal_Place obj) } } - //Disable base saves. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("...", false)] - public override void Delete(IReadOnlyCollection objs) - { - throw new NotSupportedException(); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("...", false)] - public override void Delete(int id) - { - throw new NotSupportedException(); - } - - public static Tuple GetFromFullPath(string fullPath) { IReadOnlyList shares = RepoFactory.ImportFolder.GetAll(); @@ -92,16 +85,16 @@ public static Tuple GetFromFullPath(string fullPath) foreach (SVR_ImportFolder ifolder in shares) { string importLocation = ifolder.ImportFolderLocation; - string importLocationFull = importLocation.TrimEnd('\\'); + string importLocationFull = importLocation.TrimEnd(System.IO.Path.DirectorySeparatorChar); // add back the trailing back slashes - importLocationFull = importLocationFull + "\\"; + importLocationFull = importLocationFull + $"{System.IO.Path.DirectorySeparatorChar}"; - importLocation = importLocation.TrimEnd('\\'); + importLocation = importLocation.TrimEnd(System.IO.Path.DirectorySeparatorChar); if (fullPath.StartsWith(importLocationFull, StringComparison.InvariantCultureIgnoreCase)) { string filePath = fullPath.Replace(importLocation, string.Empty); - filePath = filePath.TrimStart('\\'); + filePath = filePath.TrimStart(System.IO.Path.DirectorySeparatorChar); return new Tuple(ifolder, filePath); } } @@ -110,7 +103,10 @@ public static Tuple GetFromFullPath(string fullPath) public List GetByVideoLocal(int videolocalid) { - return VideoLocals.GetMultiple(videolocalid); + lock (Cache) + { + return VideoLocals.GetMultiple(videolocalid); + } } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Cached/VideoLocal_UserRepository.cs b/Shoko.Server/Repositories/Cached/VideoLocal_UserRepository.cs index a599af446..0fa0faeb7 100644 --- a/Shoko.Server/Repositories/Cached/VideoLocal_UserRepository.cs +++ b/Shoko.Server/Repositories/Cached/VideoLocal_UserRepository.cs @@ -40,17 +40,26 @@ public override void RegenerateDb() public List GetByVideoLocalID(int vidid) { - return VideoLocalIDs.GetMultiple(vidid); + lock (Cache) + { + return VideoLocalIDs.GetMultiple(vidid); + } } public List GetByUserID(int userid) { - return Users.GetMultiple(userid); + lock (Cache) + { + return Users.GetMultiple(userid); + } } public VideoLocal_User GetByUserIDAndVideoLocalID(int userid, int vidid) { - return UsersVideoLocals.GetOne(userid, vidid); + lock (Cache) + { + return UsersVideoLocals.GetOne(userid, vidid); + } } } } \ No newline at end of file diff --git a/Shoko.Server/Repositories/Direct/CommandRequestRepository.cs b/Shoko.Server/Repositories/Direct/CommandRequestRepository.cs index 1685ac473..58e0aa181 100644 --- a/Shoko.Server/Repositories/Direct/CommandRequestRepository.cs +++ b/Shoko.Server/Repositories/Direct/CommandRequestRepository.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using NHibernate; using NHibernate.Criterion; using Shoko.Models.Server; @@ -27,10 +28,15 @@ public CommandRequest GetByCommandID(string cmdid) public CommandRequest GetByCommandID(ISession session, string cmdid) { - CommandRequest cr = session + var crs = session .CreateCriteria(typeof(CommandRequest)) .Add(Restrictions.Eq("CommandID", cmdid)) - .UniqueResult(); + .List().ToList(); + var cr = crs.FirstOrDefault(); + if (crs.Count <= 1) return cr; + + crs.Remove(cr); + foreach(var crd in crs) Delete(crd); return cr; } diff --git a/Shoko.Server/Scanner.cs b/Shoko.Server/Scanner.cs old mode 100644 new mode 100755 index 329f83904..10c93083b --- a/Shoko.Server/Scanner.cs +++ b/Shoko.Server/Scanner.cs @@ -144,10 +144,13 @@ public void Refresh() public void AddErrorScan(ScanFile file) { - Utils.MainThreadDispatch(() => { + + Utils.MainThreadDispatch(() => + { if (ActiveScan != null && ActiveScan.ScanID == file.ScanID) ActiveErrorFiles.Add(file); }); + } public void DeleteAllErroredFiles() diff --git a/Shoko.Server/ServerSettings.cs b/Shoko.Server/ServerSettings.cs old mode 100644 new mode 100755 index 73edc9bda..302442e29 --- a/Shoko.Server/ServerSettings.cs +++ b/Shoko.Server/ServerSettings.cs @@ -34,9 +34,7 @@ public static class ServerSettings private static bool migrationError; private static bool migrationActive; - public static string Get(string key) => appSettings.ContainsKey(key) - ? appSettings[key] - : null; + public static string Get(string key) => appSettings.ContainsKey(key) ? appSettings[key] : null; public static bool Set(string key, string value) { @@ -58,8 +56,19 @@ public static bool Set(string key, string value) public static string DefaultInstance { get; set; } = Assembly.GetEntryAssembly().GetName().Name; - public static string ApplicationPath => Path.Combine( + public static string ApplicationPath + { + get + { + if (Utils.IsRunningOnMono()) + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".shoko", + DefaultInstance); + + return Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), DefaultInstance); + } + } public static string DefaultImagePath => Path.Combine(ApplicationPath, "images"); @@ -140,11 +149,15 @@ public static void LoadSettingsFromFile(string tmp_setting_file, bool delete_tmp } } // Check and see if we have old JMMServer installation and add to migration if needed - string jmmServerInstallLocation = - (string) + string jmmServerInstallLocation = null; + if (!Utils.IsRunningOnMono()) + { + jmmServerInstallLocation = (string) Registry.GetValue( @"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{898530ED-CFC7-4744-B2B8-A8D98A2FA06C}_is1", "InstallLocation", null); + } + if (!string.IsNullOrEmpty(jmmServerInstallLocation)) { @@ -381,7 +394,7 @@ public static void LoadSettingsFromFile(string tmp_setting_file, bool delete_tmp SaveSettings(); // Just in case start once for new configurations as admin to set permissions if needed - if (startedWithFreshConfig && !Utils.IsAdministrator()) + if (startedWithFreshConfig && !Utils.IsAdministrator() && !Utils.IsRunningOnMono()) { logger.Info("User has fresh config, restarting once as admin."); Utils.RestartAsAdmin(); @@ -450,7 +463,8 @@ public static void LoadLegacySettingsFromFile(bool locateAutomatically) if (locateAutomatically) { // First try to locate it from old JMM Server installer entry - string jmmServerInstallLocation = (string) Registry.GetValue( + string jmmServerInstallLocation = null; + if (!Utils.IsRunningOnMono()) jmmServerInstallLocation = (string) Registry.GetValue( @"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{898530ED-CFC7-4744-B2B8-A8D98A2FA06C}_is1", "InstallLocation", null); @@ -529,6 +543,7 @@ public static void LoadLegacySettingsFromFile(bool locateAutomatically) var col = ConfigurationManager.AppSettings; appSettings = col.AllKeys.ToDictionary(a => a, a => col[a]); logger.Error($"Error occured during LoadSettingsManuallyFromFile: {ex.Message}"); + logger.Fatal(ex.StackTrace); } } @@ -860,6 +875,9 @@ public static bool FirstRun #region Database + public static string DefaultUserUsername { get; set; } = Commons.Properties.Resources.Users_Default; + public static string DefaultUserPassword { get; set; } = string.Empty; + public static string DatabaseType { get => Get("DatabaseType"); @@ -1779,39 +1797,6 @@ public static ScheduledUpdateFrequency Trakt_SyncFrequency set => Set("Trakt_SyncFrequency", ((int) value).ToString()); } - public static bool Trakt_DownloadFanart - { - get - { - if (!bool.TryParse(Get("Trakt_DownloadFanart"), out bool val)) - val = true; // default - return val; - } - set => Set("Trakt_DownloadFanart", value.ToString()); - } - - public static bool Trakt_DownloadPosters - { - get - { - if (!bool.TryParse(Get("Trakt_DownloadPosters"), out bool val)) - val = true; // default - return val; - } - set => Set("Trakt_DownloadPosters", value.ToString()); - } - - public static bool Trakt_DownloadEpisodes - { - get - { - if (!bool.TryParse(Get("Trakt_DownloadEpisodes"), out bool val)) - val = true; // default - return val; - } - set => Set("Trakt_DownloadEpisodes", value.ToString()); - } - #endregion #region MAL @@ -1916,6 +1901,32 @@ public static string Plex_Server #endregion + public static int Linux_UID + { + get + { + if (!Int32.TryParse(Get(nameof(Linux_UID)), out int val)) return -1; + return val; + } + set { Set(nameof(Linux_UID), value.ToString()); } + } + + public static int Linux_GID + { + get + { + if (!Int32.TryParse(Get(nameof(Linux_GID)), out int val)) return -1; + return val; + } + set { Set(nameof(Linux_GID), value.ToString()); } + } + + public static int Linux_Permission + { + get { return Convert.ToInt32(Get(nameof(Linux_Permission)), 8); } + set { Set(nameof(Linux_Permission), Convert.ToString(value, 8)); } + } + public static CL_ServerSettings ToContract() { CL_ServerSettings contract = new CL_ServerSettings diff --git a/Shoko.Server/ServerState.cs b/Shoko.Server/ServerState.cs index e25b4e4be..59e3373e9 100644 --- a/Shoko.Server/ServerState.cs +++ b/Shoko.Server/ServerState.cs @@ -31,174 +31,180 @@ public void NotifyPropertyChanged(string propname) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname)); } - private bool databaseAvailable = false; public bool DatabaseAvailable { - get { return databaseAvailable; } - set { this.SetField(() => databaseAvailable, value); } + get => databaseAvailable; + set => this.SetField(() => databaseAvailable, value); } private bool serverOnline = false; public bool ServerOnline { - get { return serverOnline; } - set { this.SetField(() => serverOnline, value); } + get => serverOnline; + set => this.SetField(() => serverOnline, value); } + private bool serverStarting = false; + + public bool ServerStarting + { + get => serverStarting; + set => this.SetField(() => serverStarting, value); + } private string currentSetupStatus = string.Empty; public string CurrentSetupStatus { - get { return currentSetupStatus; } - set { this.SetField(() => currentSetupStatus, value); } + get => currentSetupStatus; + set => this.SetField(() => currentSetupStatus, value); } private bool databaseIsSQLite = false; public bool DatabaseIsSQLite { - get { return databaseIsSQLite; } - set { this.SetField(() => databaseIsSQLite, value); } + get => databaseIsSQLite; + set => this.SetField(() => databaseIsSQLite, value); } private bool databaseIsSQLServer = false; public bool DatabaseIsSQLServer { - get { return databaseIsSQLServer; } - set { this.SetField(() => databaseIsSQLServer, value); } + get => databaseIsSQLServer; + set => this.SetField(() => databaseIsSQLServer, value); } private bool databaseIsMySQL = false; public bool DatabaseIsMySQL { - get { return databaseIsMySQL; } - set { this.SetField(() => databaseIsMySQL, value); } + get => databaseIsMySQL; + set => this.SetField(() => databaseIsMySQL, value); } private string baseImagePath = string.Empty; public string BaseImagePath { - get { return baseImagePath; } - set { this.SetField(() => baseImagePath, value); } + get => baseImagePath; + set => this.SetField(() => baseImagePath, value); } private bool newVersionAvailable = false; public bool NewVersionAvailable { - get { return newVersionAvailable; } - set { this.SetField(() => newVersionAvailable, value); } + get => newVersionAvailable; + set => this.SetField(() => newVersionAvailable, value); } private string newVersionNumber = string.Empty; public string NewVersionNumber { - get { return newVersionNumber; } - set { this.SetField(() => newVersionNumber, value); } + get => newVersionNumber; + set => this.SetField(() => newVersionNumber, value); } private string newVersionDownloadLink = string.Empty; public string NewVersionDownloadLink { - get { return newVersionDownloadLink; } - set { this.SetField(() => newVersionDownloadLink, value); } + get => newVersionDownloadLink; + set => this.SetField(() => newVersionDownloadLink, value); } private string applicationVersion = string.Empty; public string ApplicationVersion { - get { return applicationVersion; } - set { this.SetField(() => applicationVersion, value); } + get => applicationVersion; + set => this.SetField(() => applicationVersion, value); } private string applicationVersionExtra = string.Empty; public string ApplicationVersionExtra { - get { return applicationVersionExtra; } - set { this.SetField(() => applicationVersionExtra, value); } + get => applicationVersionExtra; + set => this.SetField(() => applicationVersionExtra, value); } private string applicationVersionLatest = string.Empty; public string ApplicationVersionLatest { - get { return applicationVersionLatest; } - set { this.SetField(() => applicationVersionLatest, value); } + get => applicationVersionLatest; + set => this.SetField(() => applicationVersionLatest, value); } private string aniDB_Username = string.Empty; public string AniDB_Username { - get { return aniDB_Username; } - set { this.SetField(() => aniDB_Username, value); } + get => aniDB_Username; + set => this.SetField(() => aniDB_Username, value); } private string aniDB_Password = string.Empty; public string AniDB_Password { - get { return aniDB_Password; } - set { this.SetField(() => aniDB_Password, value); } + get => aniDB_Password; + set => this.SetField(() => aniDB_Password, value); } private string aniDB_ServerAddress = string.Empty; public string AniDB_ServerAddress { - get { return aniDB_ServerAddress; } - set { this.SetField(() => aniDB_ServerAddress, value); } + get => aniDB_ServerAddress; + set => this.SetField(() => aniDB_ServerAddress, value); } private string aniDB_ServerPort = string.Empty; public string AniDB_ServerPort { - get { return aniDB_ServerPort; } - set { this.SetField(() => aniDB_ServerPort, value); } + get => aniDB_ServerPort; + set => this.SetField(() => aniDB_ServerPort, value); } private string aniDB_ClientPort = string.Empty; public string AniDB_ClientPort { - get { return aniDB_ClientPort; } - set { this.SetField(() => aniDB_ClientPort, value); } + get => aniDB_ClientPort; + set => this.SetField(() => aniDB_ClientPort, value); } private string aniDB_TestStatus = string.Empty; public string AniDB_TestStatus { - get { return aniDB_TestStatus; } - set { this.SetField(() => aniDB_TestStatus, value); } + get => aniDB_TestStatus; + set => this.SetField(() => aniDB_TestStatus, value); } private bool minOnStartup = false; public bool MinOnStartup { - get { return minOnStartup; } - set { this.SetField(() => minOnStartup, value); } + get => minOnStartup; + set => this.SetField(() => minOnStartup, value); } private bool maxOnStartup = true; public bool MaxOnStartup { - get { return maxOnStartup; } - set { this.SetField(() => maxOnStartup, value); } + get => maxOnStartup; + set => this.SetField(() => maxOnStartup, value); } @@ -206,24 +212,40 @@ public bool MaxOnStartup public string VLCLocation { - get { return vLCLocation; } - set { this.SetField(() => vLCLocation, value); } + get => vLCLocation; + set => this.SetField(() => vLCLocation, value); } private bool isAutostartEnabled = false; public bool IsAutostartEnabled { - get { return isAutostartEnabled; } - set { this.SetField(() => isAutostartEnabled, value); } + get => isAutostartEnabled; + set => this.SetField(() => isAutostartEnabled, value); } private bool isAutostartDisabled = false; public bool IsAutostartDisabled { - get { return isAutostartDisabled; } - set { this.SetField(() => isAutostartDisabled, value); } + get => isAutostartDisabled; + set => this.SetField(() => isAutostartDisabled, value); + } + + private bool startupFailed = false; + + public bool StartupFailed + { + get => startupFailed; + set => this.SetField(() => startupFailed, value); + } + + private string startupFailedMessage = string.Empty; + + public string StartupFailedMessage + { + get => startupFailedMessage; + set => this.SetField(() => startupFailedMessage, value); } /* Swith this to "Registry" when we no longer need elevated run level */ @@ -233,10 +255,7 @@ public bool IsAutostartDisabled public readonly string autostartKey = "JMMServer"; - public RegistryKey AutostartRegistryKey - { - get { return Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true); } - } + public RegistryKey AutostartRegistryKey => Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true); public Dictionary ConnectedFileSystems { get; private set; } @@ -256,6 +275,8 @@ public void LoadSettings() VLCLocation = ServerSettings.VLCLocation; + if (Utils.IsRunningOnMono()) return; + if (autostartMethod == AutostartMethod.Registry) { try diff --git a/Shoko.Server/Shoko.Server.csproj b/Shoko.Server/Shoko.Server.csproj old mode 100644 new mode 100755 index 53c180a1a..de1264e8b --- a/Shoko.Server/Shoko.Server.csproj +++ b/Shoko.Server/Shoko.Server.csproj @@ -153,11 +153,11 @@ $(SolutionDir)Dependencies\Nancy.Hosting.Self.dll - - $(SolutionDir)packages\Nancy.Rest.Annotations.1.4.3.2-beta\lib\net452\Nancy.Rest.Annotations.dll + + ..\packages\Nancy.Rest.Annotations.1.4.3.7-beta\lib\net452\Nancy.Rest.Annotations.dll - - $(SolutionDir)packages\Nancy.Rest.Module.1.4.3.6-beta\lib\net452\Nancy.Rest.Module.dll + + ..\packages\Nancy.Rest.Module.1.4.3.7-beta\lib\net452\Nancy.Rest.Module.dll $(SolutionDir)packages\Nancy.Serialization.JsonNet.1.4.1\lib\net40\Nancy.Serialization.JsonNet.dll @@ -249,6 +249,7 @@ + @@ -335,6 +336,7 @@ + @@ -342,15 +344,16 @@ + - - + + @@ -375,8 +378,7 @@ - - + @@ -390,6 +392,7 @@ + @@ -520,6 +523,7 @@ + @@ -794,7 +798,9 @@ PublicResXFileCodeGenerator Resources.Designer.cs - + + Designer + Designer diff --git a/Shoko.Server/ShokoServer.cs b/Shoko.Server/ShokoServer.cs old mode 100644 new mode 100755 index 2aede6b79..b558108e0 --- a/Shoko.Server/ShokoServer.cs +++ b/Shoko.Server/ShokoServer.cs @@ -176,8 +176,11 @@ public bool StartUpServer() ServerState.Instance.DatabaseAvailable = false; ServerState.Instance.ServerOnline = false; + ServerState.Instance.ServerStarting = false; + ServerState.Instance.StartupFailed = false; + ServerState.Instance.StartupFailedMessage = string.Empty; ServerState.Instance.BaseImagePath = ImageUtils.GetBaseImagesPath(); - + downloadImagesWorker.DoWork += DownloadImagesWorker_DoWork; downloadImagesWorker.WorkerSupportsCancellation = true; @@ -220,9 +223,9 @@ public bool StartUpServer() workerSetupDB.DoWork += WorkerSetupDB_DoWork; workerSetupDB.RunWorkerCompleted += WorkerSetupDB_RunWorkerCompleted; - + ServerState.Instance.LoadSettings(); - + InitCulture(); Instance = this; @@ -237,6 +240,7 @@ public bool StartUpServer() private bool CheckBlockedFiles() { + if (Utils.IsRunningOnMono()) return true; if (Environment.OSVersion.Platform != PlatformID.Win32NT) { // do stuff on windows only @@ -273,26 +277,7 @@ private bool CheckBlockedFiles() return result; } - /* - private void BtnSyncPlexOn_Click(object sender, RoutedEventArgs routedEventArgs) - { - foreach (SVR_JMMUser user in RepoFactory.JMMUser.GetAll()) - { - if (!string.IsNullOrEmpty(user.PlexToken)) - { - new CommandRequest_PlexSyncWatched(user).Save(); - } - } - } - private void BtnSetDefault_Click(object sender, RoutedEventArgs e) - { - string imagePath = ServerSettings.DefaultImagePath; - if (!Directory.Exists(imagePath)) - Directory.CreateDirectory(imagePath); - ServerSettings.ImagesPath = imagePath; - } - */ public bool MigrateProgramDataLocation() { string oldApplicationPath = @@ -335,6 +320,7 @@ public bool MigrateProgramDataLocation() void UninstallJMMServer() { + if (Utils.IsRunningOnMono()) return; //This will be handled by the OS or user, as we cannot reliably learn what package management system they use. try { // Check in registry if installed @@ -346,6 +332,7 @@ void UninstallJMMServer() if (!string.IsNullOrEmpty(jmmServerUninstallPath)) { + // Ask if user wants to uninstall first bool res = Utils.ShowYesNo(Resources.DuplicateInstallDetectedQuestion, Resources.DuplicateInstallDetected); @@ -403,15 +390,15 @@ public bool NetPermissionWrapper(Action action) } finally { - ApplicationShutdown(); + ShutDown(); } return false; } - Utils.ShowErrorMessage("Unable to start hosting, please run JMMServer as administrator once."); - logger.Error(e); - ApplicationShutdown(); - return false; - } + Utils.ShowErrorMessage("Unable to start hosting, please run JMMServer as administrator once."); + logger.Error(e); + ShutDown(); + return false; + } return true; } @@ -421,7 +408,7 @@ public void ApplicationShutdown() { ThreadStart ts = () => { - + ServerSettings.DoServerShutdown(new ServerSettings.ReasonedEventArgs()); Environment.Exit(0); }; @@ -444,23 +431,7 @@ private void LogRotatorWorker_DoWork(object sender, DoWorkEventArgs e) } public static ShokoServer Instance { get; private set; } = new ShokoServer(); - /* - private void BtnSyncHashes_Click(object sender, RoutedEventArgs e) - { - SyncHashes(); - MessageBox.Show(Commons.Properties.Resources.Server_SyncHashesRunning, - Commons.Properties.Resources.Success, - MessageBoxButton.OK, MessageBoxImage.Information); - } - private void BtnSyncMedias_Click(object sender, RoutedEventArgs e) - { - SyncMedias(); - MessageBox.Show(Commons.Properties.Resources.Server_SyncMediasRunning, - Commons.Properties.Resources.Success, - MessageBoxButton.OK, MessageBoxImage.Information); - } - */ private void WorkerSyncHashes_DoWork(object sender, DoWorkEventArgs e) { try @@ -571,30 +542,10 @@ void WorkerFileEvents_DoWork(object sender, DoWorkEventArgs e) } } } - /* - void BtnUploadAzureCache_Click(object sender, RoutedEventArgs e) - { - IReadOnlyList allAnime = RepoFactory.AniDB_Anime.GetAll(); - int cnt = 0; - foreach (SVR_AniDB_Anime anime in allAnime) - { - cnt++; - logger.Info($"Uploading anime {cnt} of {allAnime.Count} - {anime.MainTitle}"); - try - { - CommandRequest_Azure_SendAnimeFull cmdAzure = new CommandRequest_Azure_SendAnimeFull(anime.AnimeID); - cmdAzure.Save(); - } - catch - { - } - } - } - */ void InitCulture() { - + } @@ -602,9 +553,10 @@ void InitCulture() public event EventHandler LoginFormNeeded; public event EventHandler DatabaseSetup; - public event EventHandler DBSetupCompleted; + public event EventHandler DBSetupCompleted; void WorkerSetupDB_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { + ServerState.Instance.ServerStarting = false; bool setupComplete = bool.Parse(e.Result.ToString()); if (setupComplete) { @@ -716,6 +668,9 @@ void WorkerSetupDB_DoWork(object sender, DoWorkEventArgs e) try { ServerState.Instance.ServerOnline = false; + ServerState.Instance.ServerStarting = true; + ServerState.Instance.StartupFailed = false; + ServerState.Instance.StartupFailedMessage = string.Empty; ServerState.Instance.CurrentSetupStatus = Resources.Server_Cleaning; StopWatchingFiles(); @@ -749,7 +704,7 @@ void WorkerSetupDB_DoWork(object sender, DoWorkEventArgs e) ServerState.Instance.CurrentSetupStatus = Resources.Server_DatabaseSetup; logger.Info("Setting up database..."); - if (!DatabaseFactory.InitDB()) + if (!DatabaseFactory.InitDB(out string errorMessage)) { ServerState.Instance.DatabaseAvailable = false; @@ -757,9 +712,11 @@ void WorkerSetupDB_DoWork(object sender, DoWorkEventArgs e) ServerState.Instance.CurrentSetupStatus = Resources.Server_DatabaseConfig; e.Result = false; + ServerState.Instance.StartupFailed = true; + ServerState.Instance.StartupFailedMessage = errorMessage; return; } - ServerState.Instance.DatabaseAvailable = true; + ServerState.Instance.DatabaseAvailable = true; logger.Info("Initializing Session Factory..."); Scanner.Instance.Init(); @@ -815,6 +772,8 @@ void WorkerSetupDB_DoWork(object sender, DoWorkEventArgs e) { logger.Error(ex, ex.ToString()); ServerState.Instance.CurrentSetupStatus = ex.Message; + ServerState.Instance.StartupFailed = true; + ServerState.Instance.StartupFailedMessage = $"Startup Failed: {ex}"; e.Result = false; } } @@ -1015,29 +974,7 @@ void WorkerMyAnime2_DoWork(object sender, DoWorkEventArgs e) workerMyAnime2.ReportProgress(0, ma2Progress); } } - /* - void ImportManualLinks() - { - if (workerMyAnime2.IsBusy) - { - MessageBox.Show(Commons.Properties.Resources.Server_Import, - Commons.Properties.Resources.Error, - MessageBoxButton.OK, MessageBoxImage.Error); - return; - } - - Microsoft.Win32.OpenFileDialog ofd = new Microsoft.Win32.OpenFileDialog - { - Filter = "Sqlite Files (*.DB3)|*.db3" - }; - ofd.ShowDialog(); - if (!string.IsNullOrEmpty(ofd.FileName)) - { - workerMyAnime2.RunWorkerAsync(ofd.FileName); - } - } - */ private void ImportLinksFromMA2(string databasePath) { } @@ -1229,30 +1166,7 @@ public void CheckForUpdatesNew(bool forceShowForm) } #region UI events and methods - /* - private void CommandBinding_ScanFolder(object sender, ExecutedRoutedEventArgs e) - { - object obj = e.Parameter; - if (obj == null) return; - - try - { - if (obj.GetType() == typeof(SVR_ImportFolder)) - { - SVR_ImportFolder fldr = (SVR_ImportFolder)obj; - ScanFolder(fldr.ImportFolderID); - MessageBox.Show(Commons.Properties.Resources.Server_ScanFolder, - Commons.Properties.Resources.Success, - MessageBoxButton.OK, MessageBoxImage.Information); - } - } - catch (Exception ex) - { - Utils.ShowErrorMessage(ex); - } - } - */ internal static string GetLocalIPv4(NetworkInterfaceType _type) { string output = string.Empty; @@ -1825,8 +1739,8 @@ public bool SetNancyPort(ushort port) public void CheckForUpdates() { Assembly a = Assembly.GetExecutingAssembly(); - ServerState.Instance.ApplicationVersion = Utils.GetApplicationVersion(a); - ServerState.Instance.ApplicationVersionExtra = Utils.GetApplicationExtraVersion(a); + ServerState.Instance.ApplicationVersion = Utils.GetApplicationVersion(a); + ServerState.Instance.ApplicationVersionExtra = Utils.GetApplicationExtraVersion(a); logger.Info("Checking for updates..."); CheckForUpdatesNew(false); diff --git a/Shoko.Server/UI/ServerInfo.cs b/Shoko.Server/UI/ServerInfo.cs old mode 100644 new mode 100755 diff --git a/Shoko.Server/Utilities/AVDumpHelper.cs b/Shoko.Server/Utilities/AVDumpHelper.cs index f89da88e6..96fdaa792 100644 --- a/Shoko.Server/Utilities/AVDumpHelper.cs +++ b/Shoko.Server/Utilities/AVDumpHelper.cs @@ -140,7 +140,34 @@ public static string DumpFile(string file) public static string DumpFile_Mono(string file) { - return "Not supported on Mono yet..."; + if (!File.Exists(avdumpDestination) && !GetAndExtractAVDump()) + return "Could not find or download AvDump2 CLI"; + if (string.IsNullOrEmpty(file)) + return "File path cannot be null"; + if (!File.Exists(file)) + return "Could not find Video File: " + file; + + //Create process + Process pProcess = new Process(); + pProcess.StartInfo.FileName = $"mono"; + + //strCommandParameters are parameters to pass to program + string fileName = (char)34 + file + (char)34; + + pProcess.StartInfo.Arguments = + $@"{avdumpDestination} --Auth={ServerSettings.AniDB_Username}:{ServerSettings.AniDB_AVDumpKey} --LPort={ServerSettings.AniDB_AVDumpClientPort} --PrintEd2kLink -t {fileName}"; + + pProcess.StartInfo.UseShellExecute = false; + pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + pProcess.StartInfo.RedirectStandardOutput = true; + pProcess.StartInfo.CreateNoWindow = true; + pProcess.Start(); + string strOutput = pProcess.StandardOutput.ReadToEnd(); + + //Wait for process to finish + pProcess.WaitForExit(); + + return strOutput; ; } } } \ No newline at end of file diff --git a/Shoko.Server/Utilities/LinuxFS.cs b/Shoko.Server/Utilities/LinuxFS.cs new file mode 100644 index 000000000..94140b55d --- /dev/null +++ b/Shoko.Server/Utilities/LinuxFS.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.InteropServices; + +namespace Shoko.Server.Utilities +{ + public static class LinuxFS + { + private static bool CanRun() => Environment.OSVersion.Platform == PlatformID.MacOSX || Environment.OSVersion.Platform == PlatformID.Unix; + + public static void SetLinuxPermissions(string path, int uid, int gid, int mode) + { + if (!CanRun()) return; + + Native.chown(path, uid, gid); + if (mode > 0) + Native.chmod(path, mode); + } + + public static class Native + { + [DllImport("libc", SetLastError = true)] + public static extern int chown(string path, int owner, int group); + + [DllImport("libc", SetLastError = true)] + internal static extern int chmod(string path, int mode); + } + } +} diff --git a/Shoko.Server/Utilities/TagFilter.cs b/Shoko.Server/Utilities/TagFilter.cs index 9938a08aa..cd37827dc 100644 --- a/Shoko.Server/Utilities/TagFilter.cs +++ b/Shoko.Server/Utilities/TagFilter.cs @@ -272,62 +272,58 @@ public static List ProcessTags(byte flags, List strings) } return; } - if (tag.Contains("to be") && tag.Contains("split")) + if (tag.Contains("to be") || tag.Contains("need") || tag.Contains("needs")) { - lock(toRemove) + if (tag.Contains("split")) { - toRemove.Add(a); - } - return; - } - if (tag.Contains("improved") || tag.Contains("improving") || tag.Contains("improvement")) - { - lock(toRemove) - { - toRemove.Add(a); + lock (toRemove) + { + toRemove.Add(a); + } + return; } - return; - } - if (tag.Contains("need") || tag.Contains("needs")) - { - lock(toRemove) + if (tag.Contains("improved") || tag.Contains("improving") || tag.Contains("improvement")) { - toRemove.Add(a); + lock (toRemove) + { + toRemove.Add(a); + } + return; } - return; - } - if (tag.Contains("merging") || tag.Contains("merged")) - { - lock(toRemove) + if (tag.Contains("merging") || tag.Contains("merged")) { - toRemove.Add(a); + lock(toRemove) + { + toRemove.Add(a); + } + return; } - return; - } - if (tag.Contains("deleting") || tag.Contains("deleted")) - { - lock(toRemove) + if (tag.Contains("deleting") || tag.Contains("deleted")) { - toRemove.Add(a); + lock(toRemove) + { + toRemove.Add(a); + } + return; } - return; - } - if (tag.Contains("moving") || tag.Contains("moved")) - { - lock(toRemove) + if (tag.Contains("moving") || tag.Contains("moved")) { - toRemove.Add(a); + lock(toRemove) + { + toRemove.Add(a); + } + return; } - return; - } - if (tag.Contains("improved") || tag.Contains("improving") || tag.Contains("improvement")) - { - lock(toRemove) + if (tag.Contains("improved") || tag.Contains("improving") || tag.Contains("improvement")) { - toRemove.Add(a); + lock(toRemove) + { + toRemove.Add(a); + } + return; } - return; } + if (tag.Contains("old animetags")) { lock(toRemove) diff --git a/Shoko.Server/Utils.cs b/Shoko.Server/Utils.cs old mode 100644 new mode 100755 index 3bf427d89..e4e0bc4dc --- a/Shoko.Server/Utils.cs +++ b/Shoko.Server/Utils.cs @@ -10,6 +10,10 @@ using System.Security.Principal; using System.Text; using System.Text.RegularExpressions; + +using System.Threading; +using Shoko.Models.Server; + using NLog; using NutzCode.CloudFileSystem; using Shoko.Commons.Utils; @@ -39,6 +43,8 @@ public static class Utils public static bool GrantAccess(string path) { + if (IsLinux) return true; //TODO: Implement properly, but as linux uses $HOME for the path, we should be fine. + if (Directory.Exists(path)) { List perms = Misc.RecursiveGetDirectoriesWithoutEveryonePermission(path); @@ -69,7 +75,7 @@ public static bool GrantAccess(string path) } return true; } - + public static string CalculateSHA1(string text, Encoding enc) { byte[] buffer = enc.GetBytes(text); @@ -365,7 +371,7 @@ public class ErrorEventArgs : EventArgs public string Message { get; internal set; } public string Title { get; internal set; } - + public bool IsError { get; internal set; }=true; } @@ -380,6 +386,7 @@ public CancelReasonEventArgs(string reason, string formTitle) public string Reason { get; } public string FormTitle { get; } } + public static event EventHandler ErrorMessage; public static event EventHandler YesNoRequired; @@ -391,7 +398,7 @@ public CancelReasonEventArgs(string reason, string formTitle) public static void DoEvents() { - + OnEvents?.Invoke(null,null); } @@ -424,22 +431,24 @@ public static void ShowErrorMessage(string msg) ErrorMessage?.Invoke(null, new ErrorEventArgs { Message = msg }); logger.Error(msg); } + public static void ShowErrorMessage(string title, string msg) { //MessageBox.Show(msg, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); ErrorMessage?.Invoke(null, new ErrorEventArgs { Message = msg , Title=title }); logger.Error(msg); } + public static void ShowMessage(string title, string msg) { //MessageBox.Show(msg, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); ErrorMessage?.Invoke(null, new ErrorEventArgs { Message = msg, Title = title, IsError=false}); logger.Error(msg); } + public static string GetApplicationVersion(Assembly a) { - AssemblyName an = a.GetName(); - return an.Version.ToString(); + return a.GetName().Version.ToString(); } public static string GetApplicationExtraVersion(Assembly a) @@ -454,6 +463,16 @@ public static string GetApplicationExtraVersion(Assembly a) return version.InformationalVersion; } + public static string GetApplicationVersion() + { + return GetApplicationVersion(Assembly.GetExecutingAssembly()); + } + + public static string GetApplicationExtraVersion() + { + return GetApplicationExtraVersion(Assembly.GetExecutingAssembly()); + } + public static long GetCurrentUTCTime() { DateTime dt = DateTime.Now.ToUniversalTime(); @@ -569,7 +588,7 @@ public static string FormatSecondsToDisplayTime(int secs) if (t.Hours > 0) return $"{t.Hours}:{t.Minutes.ToString().PadLeft(2, '0')}:{t.Seconds.ToString().PadLeft(2, '0')}"; - return $"{t.Minutes}:{t.Seconds.ToString().PadLeft(2, '0')}"; + return $"{t.Minutes}:{t.Seconds.ToString().PadLeft(2, '0')}"; } public static string FormatAniDBRating(double rat) @@ -584,7 +603,7 @@ public static string FormatAniDBRating(double rat) { if (string.IsNullOrEmpty(sint)) return null; - return int.Parse(sint); + return int.Parse(sint); } public static string RemoveInvalidFolderNameCharacters(string folderName) @@ -615,9 +634,9 @@ public static string ReplaceInvalidFolderNameCharacters(string folderName) ret = ret.Replace(@"<", @"‹"); ret = ret.Replace(@"?", @"﹖"); ret = ret.Replace(@"...", @"…"); - if (ret.StartsWith(".")) ret = "․" + ret.Substring(1, ret.Length - 1); - while (ret.EndsWith(".")) - ret = ret.Substring(0, ret.Length - 1) + '․'; + if (ret.StartsWith(".", StringComparison.Ordinal)) ret = "․" + ret.Substring(1, ret.Length - 1); + if (ret.EndsWith(".", StringComparison.Ordinal)) + ret = ret.Substring(0, ret.Length - 1) + "․"; return ret.Trim(); } @@ -729,19 +748,19 @@ public static string GetMd5Hash(string input) public static string GetMd5Hash(MD5 md5Hash, string input) { - // Convert the input string to a byte array and compute the hash. + // Convert the input string to a byte array and compute the hash. byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input)); - // Create a new Stringbuilder to collect the bytes + // Create a new Stringbuilder to collect the bytes // and create a string. StringBuilder sBuilder = new StringBuilder(); - // Loop through each byte of the hashed data - // and format each one as a hexadecimal string. + // Loop through each byte of the hashed data + // and format each one as a hexadecimal string. for (int i = 0; i < data.Length; i++) sBuilder.Append(data[i].ToString("x2")); - // Return the hexadecimal string. + // Return the hexadecimal string. return sBuilder.ToString(); } @@ -780,11 +799,63 @@ static extern long StrFormatByteSize(long fileSize, public static string FormatByteSize(long fileSize) { + if (IsRunningOnMono()) return GetBytesReadable(fileSize); + StringBuilder sbBuffer = new StringBuilder(20); StrFormatByteSize(fileSize, sbBuffer, 20); return sbBuffer.ToString(); } + // Returns the human-readable file size for an arbitrary, 64-bit file size + // The default format is "0.### XB", e.g. "4.2 KB" or "1.434 GB" + // http://www.somacon.com/p576.php + public static string GetBytesReadable(long i) + { + // Get absolute value + long absolute_i = (i < 0 ? -i : i); + // Determine the suffix and readable value + string suffix; + double readable; + if (absolute_i >= 0x1000000000000000) // Exabyte + { + suffix = "EB"; + readable = (i >> 50); + } + else if (absolute_i >= 0x4000000000000) // Petabyte + { + suffix = "PB"; + readable = (i >> 40); + } + else if (absolute_i >= 0x10000000000) // Terabyte + { + suffix = "TB"; + readable = (i >> 30); + } + else if (absolute_i >= 0x40000000) // Gigabyte + { + suffix = "GB"; + readable = (i >> 20); + } + else if (absolute_i >= 0x100000) // Megabyte + { + suffix = "MB"; + readable = (i >> 10); + } + else if (absolute_i >= 0x400) // Kilobyte + { + suffix = "KB"; + readable = i; + } + else + { + return i.ToString("0 B"); // Byte + } + // Divide by 1024 to get fractional value + readable = (readable / 1024); + // Return formatted number with suffix + return readable.ToString("0.### ") + suffix; + } + #endregion public static List GetPossibleSubtitleFiles(string fileName) @@ -971,6 +1042,8 @@ public static string[] Tail(this TextReader reader, int lineCount) public static void RestartAsAdmin() { + if (Utils.IsRunningOnMono()) return; //Again, mono cannot handle this. + string BatchFile = Path.Combine(System.IO.Path.GetTempPath(), "RestartAsAdmin.bat"); var exeName = Process.GetCurrentProcess().MainModule.FileName; @@ -1001,7 +1074,7 @@ public static void RestartAsAdmin() } proc.Start(); - ServerSettings.DoServerShutdown(new ServerSettings.ReasonedEventArgs()); + ServerSettings.DoServerShutdown(new ServerSettings.ReasonedEventArgs()); Environment.Exit(0); } catch (Exception ex) @@ -1023,7 +1096,7 @@ public static bool IsDirectoryWritable(string dirPath, bool throwIfFails = false { if (throwIfFails) throw; - return false; + return false; } } diff --git a/Shoko.Server/app.config b/Shoko.Server/app.config index ff7e5858f..1527cc705 100644 --- a/Shoko.Server/app.config +++ b/Shoko.Server/app.config @@ -60,6 +60,9 @@ + + + @@ -132,10 +135,16 @@ - + + + + + + + + - - + diff --git a/Shoko.Server/packages.config b/Shoko.Server/packages.config index 42421149e..e94f4685a 100644 --- a/Shoko.Server/packages.config +++ b/Shoko.Server/packages.config @@ -13,8 +13,8 @@ - - + + diff --git a/Shoko.UI/App.config b/Shoko.UI/App.config index ea80bb13c..10779e7a6 100644 --- a/Shoko.UI/App.config +++ b/Shoko.UI/App.config @@ -3,7 +3,6 @@
- @@ -104,4 +103,16 @@ --> - + + + + + + + + + + + + + \ No newline at end of file diff --git a/Shoko.UI/Forms/AboutForm.xaml b/Shoko.UI/Forms/AboutForm.xaml index b30255221..2c95f8971 100644 --- a/Shoko.UI/Forms/AboutForm.xaml +++ b/Shoko.UI/Forms/AboutForm.xaml @@ -9,6 +9,12 @@ Width="Auto" Height="Auto" Title="{Resx ResxName=Shoko.Commons.Properties.Resources, Key=ShokoServer}" Name="aboutWindow" Icon="/ShokoServer;component/db.ico"> + + + + + + @@ -46,11 +52,35 @@ - - - + diff --git a/Shoko.UI/Forms/AboutForm.xaml.cs b/Shoko.UI/Forms/AboutForm.xaml.cs index dbde604d1..0bf5c3d82 100644 --- a/Shoko.UI/Forms/AboutForm.xaml.cs +++ b/Shoko.UI/Forms/AboutForm.xaml.cs @@ -1,4 +1,8 @@ -using System.Windows; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; using Shoko.Server; namespace Shoko.UI.Forms @@ -40,5 +44,36 @@ private void cbUpdateChannel_SelectionChanged(object sender, if (!string.IsNullOrEmpty(cbUpdateChannel.Text)) ServerSettings.UpdateChannel = cbUpdateChannel.Text; } + + private void CommandBinding_SelectTextAndCopy(object sender, ExecutedRoutedEventArgs e) + { + string obj = e.Parameter as string; + CopyToClipboard(obj); + } + + public static void CopyToClipboard(string obj) + { + CopyToClipboardRecursiveRetry(obj, 0, 5); + } + + private static void CopyToClipboardRecursiveRetry(string obj, int retryCount, int maxRetryCount) + { + if (obj == null) return; + obj = obj.Replace('`', '\''); + try + { + Clipboard.Clear(); + Thread.Sleep(50); + Clipboard.SetDataObject(obj); + } + catch (COMException ex) + { + if (retryCount < maxRetryCount) + { + Thread.Sleep(200); + CopyToClipboardRecursiveRetry(obj, retryCount + 1, maxRetryCount); + } + } + } } } \ No newline at end of file diff --git a/Shoko.UI/Forms/AuthProvider.cs b/Shoko.UI/Forms/AuthProvider.cs index a9f5092b8..6b6b5041d 100644 --- a/Shoko.UI/Forms/AuthProvider.cs +++ b/Shoko.UI/Forms/AuthProvider.cs @@ -6,7 +6,11 @@ namespace Shoko.UI.Forms { public class AuthProvider : IOAuthProvider { - public string Name => "WPF"; + public string Name + { + get { return "WPF"; } + } + private Window _owner; public async Task Login(AuthRequest request) diff --git a/Shoko.UI/Forms/CloudAccountForm.xaml.cs b/Shoko.UI/Forms/CloudAccountForm.xaml.cs index 647d33bb8..7bde12dc0 100644 --- a/Shoko.UI/Forms/CloudAccountForm.xaml.cs +++ b/Shoko.UI/Forms/CloudAccountForm.xaml.cs @@ -147,8 +147,14 @@ public void SetConnectStatus() this.OnPropertyChanged(() => IsConnected, () => IsNotConnected); } - public bool EnableConnect => (comboProvider.SelectedIndex >= 0 && - !string.IsNullOrEmpty(txtCloudAccountName.Text)); + public bool EnableConnect + { + get + { + return (comboProvider.SelectedIndex >= 0 && + !string.IsNullOrEmpty(txtCloudAccountName.Text)); + } + } private SVR_CloudAccount WorkingAccount; private SVR_CloudAccount SaveAccount; diff --git a/Shoko.UI/MainWindow.xaml.cs b/Shoko.UI/MainWindow.xaml.cs old mode 100644 new mode 100755 index cc242c69f..c03a82a5f --- a/Shoko.UI/MainWindow.xaml.cs +++ b/Shoko.UI/MainWindow.xaml.cs @@ -512,7 +512,7 @@ private void SetCulture() if (result != System.Windows.Forms.DialogResult.OK) return; System.Windows.Forms.Application.Restart(); - ShokoServer.Instance.ApplicationShutdown(); + //ShokoServer.Instance.s(); } } catch (Exception ex) @@ -1078,7 +1078,10 @@ void MainWindow_StateChanged(object sender, EventArgs e) Show(); } - void TippuTrayNotify_MouseDoubleClick(object sender, MouseEventArgs e) => Show(); + void TippuTrayNotify_MouseDoubleClick(object sender, MouseEventArgs e) + { + this.Show(); + } private void CreateMenus() { diff --git a/Shoko.UI/Properties/AssemblyInfo.cs b/Shoko.UI/Properties/AssemblyInfo.cs index c01690efb..58796c1c3 100644 --- a/Shoko.UI/Properties/AssemblyInfo.cs +++ b/Shoko.UI/Properties/AssemblyInfo.cs @@ -7,6 +7,7 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. +[assembly: AssemblyTitle("Shoko Server")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from diff --git a/Shoko.UI/Shoko.UI.csproj b/Shoko.UI/Shoko.UI.csproj index 0c9fc3c4f..ecd1cc24f 100644 --- a/Shoko.UI/Shoko.UI.csproj +++ b/Shoko.UI/Shoko.UI.csproj @@ -95,11 +95,13 @@ $(SolutionDir)packages\NLog.4.4.11\lib\net45\NLog.dll + +