This commit is contained in:
TidusJar 2018-08-22 13:30:28 +01:00
commit 0a4acb314c
221 changed files with 12313 additions and 15406 deletions

View file

@ -1,5 +1,215 @@
# Changelog # Changelog
## v3.0.3587 (2018-08-19)
### **New Features**
- Added the ability to invite Plex Friends from the user management screen. [Jamie]
- Added rich notifications for mobile. [Jamie]
- Updater fixes. [Jamie]
- Added updater test mode. [Jamie Rees]
- Added a new API method to delete issue comments. [TidusJar]
- Updated @ngu/carousel to beta version to remove rxjs-compat dependency. [Matt Jeanes]
- Update to Angular 6/Webpack 4. [Matt Jeanes]
- Update CHANGELOG.md. [Jamie]
- Updated the way we create the wizard user, errors show now be fed back to the user. [Jamie]
- Added Brazillian Portuguese as a language and also Polish. [Jamie]
- Updated swagger. [Jamie]
- Updated to 2.1.1. [Jamie]
### **Fixes**
- #2408 Added the feature to delete comments on issues. [Jamie]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (French) [Jamie]
- Fixed #2440. [TidusJar]
- Delete cake.config. [Chris Pritchard]
- Initial attempt at getting anime seriestype working. [Chris Pritchard]
- Add cake.config. [Chris Pritchard]
- Fixed the issue where we wouldn't correctly mark some shows as available when there was no provider id #2429. [Jamie]
- Fixed the 'loop' in the cacher #2429. [Jamie]
- Fixed #2427. [Jamie]
- Fixed #2424. [Jamie]
- Fixed #2409. [Jamie]
- More updater. [Jamie]
- Humanize the request type enum in notifications e.g. TvShow will now appear as "Tv Show" #2416. [TidusJar]
- Made the quality override and root folder override load when we load the show (It will now appear) [Jamie]
- Fixed #2415 where power users could not set the Sonarr Quality Override or Root Folder Override. [Jamie]
- #2371 Fixed the issue where certain actions would not setup the series correctly in Sonarr. [Jamie]
- Tightened up the security from an API perspecitve. [TidusJar]
- Stop the root folder and profile calls from erroring. [TidusJar]
- New translations en.json (Polish) [Jamie]
- New translations en.json (Polish) [Jamie]
- New translations en.json (Polish) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- Fixed all linting. [TidusJar]
- Comment out envparam stuff. [Matt Jeanes]
- Fixed prod build issue. [Matt Jeanes]
- Missed a tiny bit. [Matt Jeanes]
- Fix test. [Matt Jeanes]
- Fix test build. [Matt Jeanes]
- Linting + remove debug. [Matt Jeanes]
- Switch to Yarn and disable auto publish in release mode. [Matt Jeanes]
- Fix for #2409. [TidusJar]
- New translations en.json (Swedish) [Jamie]
- New translations en.json (Spanish) [Jamie]
- New translations en.json (Portuguese, Brazilian) [Jamie]
- New translations en.json (Polish) [Jamie]
- New translations en.json (Norwegian) [Jamie]
- New translations en.json (Italian) [Jamie]
- New translations en.json (German) [Jamie]
- New translations en.json (French) [Jamie]
- New translations en.json (Dutch) [Jamie]
- New translations en.json (Danish) [Jamie]
- Possible fix for #2298. [D34DC3N73R]
- Fixed the text for #2370. [Jamie]
- Fixed where you couldn't bulk edit the limits to 0 #2318. [Jamie]
- Upgraded to .net 2.1.2 (Includes security fixes) [Jamie]
## v3.0.3477 (2018-07-18)
### **New Features**
- Updated the Emby availability checker to bring it more in line with what we do with Plex. [TidusJar]
- Added the ability to impersonate a user when using the API Key. This allows people to use the API and request as a certain user. #2363. [Jamie Rees]
- Added more background images and it will loop through the available ones. [Jamie Rees]
- Added chunk hashing to resolve #2330. [Jamie Rees]
- Added API at /api/v1/status/info to get branch and version information #2331. [Jamie Rees]
- Update to .net 2.1.1. [Jamie]
### **Fixes**
- Fix #2322 caused by continue statement inside try catch block. [Anojh]
- Fixed #2367. [TidusJar]
- Fixed the issue where you could not delete a user #2365. [TidusJar]
- Another attempt to fix #2366. [Jamie Rees]
- Fixed the Plex OAuth warning. [Jamie]
- Revert "Fixed Plex OAuth, should no longer show Insecure warning" [Jamie Rees]
- Fixed Plex OAuth, should no longer show Insecure warning. [Jamie Rees]
- Fixed the View On Emby URL since the Link changed #2368. [Jamie Rees]
- Fixed the issue where episodes were not being marked as available in the search #2367. [Jamie Rees]
- Fixed #2371. [Jamie Rees]
- Fixed collection issues in Emby #2366. [Jamie Rees]
- Do not delete the Emby Information every time we run, let's keep the content now. [Jamie Rees]
- Emby Improvements: Batch up the amount we get from the server. [Jamie Rees]
- Log errors when they are uncaught. [Jamie Rees]
- Fix unclosed table tags causing overflow #2322. [Anojh]
- This should now fix #2350. [Jamie]
- Improve the validation around the Application URL. [Jamie Rees]
- Fixed #2341. [Jamie Rees]
- Stop spamming errors when FanArt doesn't have the image. [Jamie Rees]
- Fixed #2338. [Jamie Rees]
- Removed some logging statements. [Jamie Rees]
- Fixed the api key being case sensative #2350. [Jamie Rees]
- Improved the Emby API #2230 Thanks Luke! [Jamie Rees]
- Revert. [Jamie Rees]
- Fixed a small error in the Mobile Notification Provider. [Jamie Rees]
- Minor style tweaks. [Randall Bruder]
- Downgrade to .net core 2.0. [Jamie Rees]
- Downgrade Microsoft.AspNetCore.All package back to 2.0.8. [Jamie Rees]
- Removed old code. [Jamie Rees]
- Swap out the old way of validating the API key with a real middlewear this time. [Jamie Rees]
## v3.0.3421 (2018-06-23) ## v3.0.3421 (2018-06-23)
### **New Features** ### **New Features**

View file

@ -15,19 +15,19 @@ test: off
after_build: after_build:
- cmd: >- - cmd: >-
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\windows.zip" appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\windows.zip"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\osx.tar.gz" appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\osx.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux.tar.gz" appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\linux.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm.tar.gz" appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\linux-arm.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\windows-32bit.zip" appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\windows-32bit.zip"
# appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm64.tar.gz" # appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm64.tar.gz"

View file

@ -1,10 +1,10 @@
#tool "nuget:?package=GitVersion.CommandLine" #tool "nuget:?package=GitVersion.CommandLine"
#addin "Cake.Gulp" #addin "Cake.Gulp"
#addin "nuget:?package=Cake.Npm&version=0.13.0"
#addin "SharpZipLib" #addin "SharpZipLib"
#addin nuget:?package=Cake.Compression&version=0.1.4 #addin nuget:?package=Cake.Compression&version=0.1.4
#addin "Cake.Incubator" #addin "Cake.Incubator"
#addin "Cake.Yarn"
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// ARGUMENTS // ARGUMENTS
@ -26,7 +26,7 @@ var csProj = "./src/Ombi/Ombi.csproj"; // Path to the project.csproj
var solutionFile = "Ombi.sln"; // Solution file if needed var solutionFile = "Ombi.sln"; // Solution file if needed
GitVersion versionInfo = null; GitVersion versionInfo = null;
var frameworkVer = "netcoreapp2.0"; var frameworkVer = "netcoreapp2.1";
var buildSettings = new DotNetCoreBuildSettings var buildSettings = new DotNetCoreBuildSettings
{ {
@ -122,36 +122,19 @@ Task("SetVersionInfo")
Task("NPM") Task("NPM")
.Does(() => { .Does(() => {
var settings = new NpmInstallSettings { Yarn.FromPath(webProjDir).Install();
LogLevel = NpmLogLevel.Silent,
WorkingDirectory = webProjDir,
Production = true
};
NpmInstall(settings);
}); });
Task("Gulp Publish") Task("Gulp Publish")
.IsDependentOn("NPM") .IsDependentOn("NPM")
.Does(() => { .Does(() => {
Yarn.FromPath(webProjDir).RunScript("publish");
var runScriptSettings = new NpmRunScriptSettings {
ScriptName="publish",
WorkingDirectory = webProjDir,
};
NpmRunScript(runScriptSettings);
}); });
Task("TSLint") Task("TSLint")
.Does(() => .Does(() =>
{ {
var settings = new NpmRunScriptSettings { Yarn.FromPath(webProjDir).RunScript("lint");
WorkingDirectory = webProjDir,
ScriptName = "lint"
};
NpmRunScript(settings);
}); });
Task("PrePublish") Task("PrePublish")
@ -178,7 +161,7 @@ Task("Publish")
.IsDependentOn("Publish-OSX") .IsDependentOn("Publish-OSX")
.IsDependentOn("Publish-Linux") .IsDependentOn("Publish-Linux")
.IsDependentOn("Publish-Linux-ARM") .IsDependentOn("Publish-Linux-ARM")
//.IsDependentOn("Publish-Linux-ARM-64Bit") .IsDependentOn("Publish-Linux-ARM-64Bit")
.IsDependentOn("Package"); .IsDependentOn("Package");
Task("Publish-Windows") Task("Publish-Windows")
@ -189,6 +172,8 @@ Task("Publish-Windows")
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
CopyFile(buildDir + "/"+frameworkVer+"/win10-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x64/published/Swagger.xml"); CopyFile(buildDir + "/"+frameworkVer+"/win10-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x64/published/Swagger.xml");
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/win10-x64/published/updater");
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
}); });
@ -200,6 +185,9 @@ Task("Publish-Windows-32bit")
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
CopyFile(buildDir + "/"+frameworkVer+"/win10-x86/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x86/published/Swagger.xml"); CopyFile(buildDir + "/"+frameworkVer+"/win10-x86/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x86/published/Swagger.xml");
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/win10-x86/published/updater");
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
}); });
@ -211,6 +199,8 @@ Task("Publish-OSX")
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
CopyFile(buildDir + "/"+frameworkVer+"/osx-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/osx-x64/published/Swagger.xml"); CopyFile(buildDir + "/"+frameworkVer+"/osx-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/osx-x64/published/Swagger.xml");
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/osx-x64/published/updater");
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
}); });
@ -222,6 +212,8 @@ Task("Publish-Linux")
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
CopyFile(buildDir + "/"+frameworkVer+"/linux-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/linux-x64/published/Swagger.xml"); CopyFile(buildDir + "/"+frameworkVer+"/linux-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/linux-x64/published/Swagger.xml");
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/linux-x64/published/updater");
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
}); });
@ -235,6 +227,8 @@ Task("Publish-Linux-ARM")
CopyFile( CopyFile(
buildDir + "/"+frameworkVer+"/linux-arm/Swagger.xml", buildDir + "/"+frameworkVer+"/linux-arm/Swagger.xml",
buildDir + "/"+frameworkVer+"/linux-arm/published/Swagger.xml"); buildDir + "/"+frameworkVer+"/linux-arm/published/Swagger.xml");
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/linux-arm/published/updater");
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
}); });
@ -248,6 +242,8 @@ Task("Publish-Linux-ARM-64Bit")
CopyFile( CopyFile(
buildDir + "/"+frameworkVer+"/linux-arm64/Swagger.xml", buildDir + "/"+frameworkVer+"/linux-arm64/Swagger.xml",
buildDir + "/"+frameworkVer+"/linux-arm64/published/Swagger.xml"); buildDir + "/"+frameworkVer+"/linux-arm64/published/Swagger.xml");
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/linux-arm64/published/updater");
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
}); });

291
package-lock.json generated Normal file
View file

@ -0,0 +1,291 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
},
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "1.0.3"
}
},
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
"integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
"requires": {
"chalk": "1.1.3",
"esutils": "2.0.2",
"js-tokens": "3.0.2"
},
"dependencies": {
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"requires": {
"ansi-styles": "2.2.1",
"escape-string-regexp": "1.0.5",
"has-ansi": "2.0.0",
"strip-ansi": "3.0.1",
"supports-color": "2.0.0"
}
}
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "1.0.0",
"concat-map": "0.0.1"
}
},
"builtin-modules": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8="
},
"chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.4.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"requires": {
"color-convert": "1.9.1"
}
},
"supports-color": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"color-convert": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
"integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"commander": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"diff": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA=="
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"esprima": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
"integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw=="
},
"esutils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"has-ansi": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
"requires": {
"ansi-regex": "2.1.1"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
},
"js-yaml": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz",
"integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==",
"requires": {
"argparse": "1.0.10",
"esprima": "4.0.0"
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "1.1.11"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1.0.2"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-parse": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME="
},
"resolve": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz",
"integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==",
"requires": {
"path-parse": "1.0.5"
}
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "2.1.1"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
},
"tslib": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz",
"integrity": "sha512-AVP5Xol3WivEr7hnssHDsaM+lVrVXWUvd1cfXTRkTj80b//6g2wIFEH6hZG0muGZRnHGrfttpdzRk3YlBkWjKw=="
},
"tslint": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.10.0.tgz",
"integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=",
"requires": {
"babel-code-frame": "6.26.0",
"builtin-modules": "1.1.1",
"chalk": "2.4.1",
"commander": "2.15.1",
"diff": "3.5.0",
"glob": "7.1.2",
"js-yaml": "3.12.0",
"minimatch": "3.0.4",
"resolve": "1.7.1",
"semver": "5.5.0",
"tslib": "1.9.2",
"tsutils": "2.27.1"
}
},
"tsutils": {
"version": "2.27.1",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz",
"integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==",
"requires": {
"tslib": "1.9.2"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}

View file

@ -91,27 +91,31 @@ namespace Ombi.Api.Emby
request.AddContentHeader("Content-Type", "application/json"); request.AddContentHeader("Content-Type", "application/json");
} }
public async Task<EmbyItemContainer<MovieInformation>> GetCollection(string mediaId, string apiKey, string userId, string baseUrl) public async Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId, string apiKey, string userId, string baseUrl)
{ {
var request = new Request($"emby/users/{userId}/items?parentId={mediaId}", baseUrl, HttpMethod.Get); var request = new Request($"emby/users/{userId}/items?parentId={mediaId}", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey); AddHeaders(request, apiKey);
return await Api.Request<EmbyItemContainer<MovieInformation>>(request); request.AddQueryString("Fields", "ProviderIds,Overview");
request.AddQueryString("VirtualItem", "False");
return await Api.Request<EmbyItemContainer<EmbyMovie>>(request);
} }
public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, string userId, string baseUri) public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri)
{ {
return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri, true); return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count);
} }
public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, string userId, string baseUri) public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri)
{ {
return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri); return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count);
} }
public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, string userId, string baseUri) public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri)
{ {
return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri); return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri, false, startIndex, count);
} }
public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl) public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl)
@ -150,6 +154,24 @@ namespace Ombi.Api.Emby
AddHeaders(request, apiKey); AddHeaders(request, apiKey);
var obj = await Api.Request<EmbyItemContainer<T>>(request);
return obj;
}
private async Task<EmbyItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count)
{
var request = new Request($"emby/users/{userId}/items", baseUri, HttpMethod.Get);
request.AddQueryString("Recursive", true.ToString());
request.AddQueryString("IncludeItemTypes", type);
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
request.AddQueryString("startIndex", startIndex.ToString());
request.AddQueryString("limit", count.ToString());
request.AddQueryString("VirtualItem", "False");
AddHeaders(request, apiKey);
var obj = await Api.Request<EmbyItemContainer<T>>(request); var obj = await Api.Request<EmbyItemContainer<T>>(request);
return obj; return obj;
} }

View file

@ -14,12 +14,17 @@ namespace Ombi.Api.Emby
Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri); Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri);
Task<EmbyConnectUser> LoginConnectUser(string username, string password); Task<EmbyConnectUser> LoginConnectUser(string username, string password);
Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, string userId, string baseUri); Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId,
Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, string userId, string baseUri); string baseUri);
Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, string userId, string baseUri);
Task<EmbyItemContainer<MovieInformation>> GetCollection(string mediaId, string apiKey, string userId, Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId,
string baseUrl); string baseUri);
Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, int startIndex, int count, string userId,
string baseUri);
Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId,
string apiKey, string userId, string baseUrl);
Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl); Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl);
Task<MovieInformation> GetMovieInformation(string mediaId, string apiKey, string userId, string baseUrl); Task<MovieInformation> GetMovieInformation(string mediaId, string apiKey, string userId, string baseUrl);

View file

@ -6,6 +6,6 @@ namespace Ombi.Api.Notifications
{ {
public interface IOneSignalApi public interface IOneSignalApi
{ {
Task<OneSignalNotificationResponse> PushNotification(List<string> playerIds, string message); Task<OneSignalNotificationResponse> PushNotification(List<string> playerIds, string message, bool isAdminNotification, int requestId, int requestType);
} }
} }

View file

@ -4,18 +4,22 @@
{ {
public string app_id { get; set; } public string app_id { get; set; }
public string[] include_player_ids { get; set; } public string[] include_player_ids { get; set; }
public Data data { get; set; } public object data { get; set; }
public Button[] buttons { get; set; }
public Contents contents { get; set; } public Contents contents { get; set; }
} }
public class Data
{
public string foo { get; set; }
}
public class Contents public class Contents
{ {
public string en { get; set; } public string en { get; set; }
} }
public class Button
{
public string id { get; set; }
public string text { get; set; }
//public string icon { get; set; }
}
} }

View file

@ -4,6 +4,10 @@
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Humanizer.Core" Version="2.4.2" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" /> <ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup> </ItemGroup>

View file

@ -20,7 +20,7 @@ namespace Ombi.Api.Notifications
private readonly IApplicationConfigRepository _appConfig; private readonly IApplicationConfigRepository _appConfig;
private const string ApiUrl = "https://onesignal.com/api/v1/notifications"; private const string ApiUrl = "https://onesignal.com/api/v1/notifications";
public async Task<OneSignalNotificationResponse> PushNotification(List<string> playerIds, string message) public async Task<OneSignalNotificationResponse> PushNotification(List<string> playerIds, string message, bool isAdminNotification, int requestId, int requestType)
{ {
if (!playerIds.Any()) if (!playerIds.Any())
{ {
@ -39,6 +39,17 @@ namespace Ombi.Api.Notifications
include_player_ids = playerIds.ToArray() include_player_ids = playerIds.ToArray()
}; };
if (isAdminNotification)
{
// Add the action buttons
body.data = new { requestid = requestId, requestType = requestType};
body.buttons = new[]
{
new Button {id = "approve", text = "Approve Request"},
new Button {id = "deny", text = "Deny Request"},
};
}
request.AddJsonBody(body); request.AddJsonBody(body);
var result = await _api.Request<OneSignalNotificationResponse>(request); var result = await _api.Request<OneSignalNotificationResponse>(request);

View file

@ -11,6 +11,7 @@ namespace Ombi.Api.Plex
public interface IPlexApi public interface IPlexApi
{ {
Task<PlexStatus> GetStatus(string authToken, string uri); Task<PlexStatus> GetStatus(string authToken, string uri);
Task<PlexLibrariesForMachineId> GetLibrariesForMachineId(string authToken, string machineId);
Task<PlexAuthentication> SignIn(UserRequest user); Task<PlexAuthentication> SignIn(UserRequest user);
Task<PlexServer> GetServer(string authToken); Task<PlexServer> GetServer(string authToken);
Task<PlexContainer> GetLibrarySections(string authToken, string plexFullHost); Task<PlexContainer> GetLibrarySections(string authToken, string plexFullHost);
@ -22,8 +23,8 @@ namespace Ombi.Api.Plex
Task<PlexFriends> GetUsers(string authToken); Task<PlexFriends> GetUsers(string authToken);
Task<PlexAccount> GetAccount(string authToken); Task<PlexAccount> GetAccount(string authToken);
Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId); Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId);
Task<OAuthPin> CreatePin();
Task<OAuthPin> GetPin(int pinId); Task<OAuthPin> GetPin(int pinId);
Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard); Task<Uri> GetOAuthUrl(int pinId, string code, string applicationUrl);
Task<PlexAddWrapper> AddUser(string emailAddress, string serverId, string authToken, int[] libs);
} }
} }

View file

@ -0,0 +1,84 @@
using System.Collections.Generic;
using System.Xml.Serialization;
namespace Ombi.Api.Plex.Models
{
[XmlRoot(ElementName = "Section")]
public class Section
{
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "key")]
public string Key { get; set; }
[XmlAttribute(AttributeName = "title")]
public string Title { get; set; }
[XmlAttribute(AttributeName = "type")]
public string Type { get; set; }
[XmlAttribute(AttributeName = "shared")]
public string Shared { get; set; }
}
[XmlRoot(ElementName = "SharedServer")]
public class SharedServer
{
[XmlElement(ElementName = "Section")]
public List<Section> Section { get; set; }
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "username")]
public string Username { get; set; }
[XmlAttribute(AttributeName = "email")]
public string Email { get; set; }
[XmlAttribute(AttributeName = "userID")]
public string UserID { get; set; }
[XmlAttribute(AttributeName = "accessToken")]
public string AccessToken { get; set; }
[XmlAttribute(AttributeName = "name")]
public string Name { get; set; }
[XmlAttribute(AttributeName = "acceptedAt")]
public string AcceptedAt { get; set; }
[XmlAttribute(AttributeName = "invitedAt")]
public string InvitedAt { get; set; }
[XmlAttribute(AttributeName = "allowSync")]
public string AllowSync { get; set; }
[XmlAttribute(AttributeName = "allowCameraUpload")]
public string AllowCameraUpload { get; set; }
[XmlAttribute(AttributeName = "allowChannels")]
public string AllowChannels { get; set; }
[XmlAttribute(AttributeName = "allowTuners")]
public string AllowTuners { get; set; }
[XmlAttribute(AttributeName = "owned")]
public string Owned { get; set; }
}
[XmlRoot(ElementName = "MediaContainer")]
public class PlexAdd
{
[XmlElement(ElementName = "SharedServer")]
public SharedServer SharedServer { get; set; }
[XmlAttribute(AttributeName = "friendlyName")]
public string FriendlyName { get; set; }
[XmlAttribute(AttributeName = "identifier")]
public string Identifier { get; set; }
[XmlAttribute(AttributeName = "machineIdentifier")]
public string MachineIdentifier { get; set; }
[XmlAttribute(AttributeName = "size")]
public string Size { get; set; }
}
[XmlRoot(ElementName = "Response")]
public class AddUserError
{
[XmlAttribute(AttributeName = "code")]
public string Code { get; set; }
[XmlAttribute(AttributeName = "status")]
public string Status { get; set; }
}
public class PlexAddWrapper
{
public PlexAdd Add { get; set; }
public AddUserError Error { get; set; }
public bool HasError => Error != null;
}
}

View file

@ -0,0 +1,66 @@
namespace Ombi.Api.Plex.Models
{
using System;
using System.Xml.Serialization;
using System.Collections.Generic;
[XmlRoot(ElementName = "Section")]
public class SectionLite
{
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "key")]
public string Key { get; set; }
[XmlAttribute(AttributeName = "type")]
public string Type { get; set; }
[XmlAttribute(AttributeName = "title")]
public string Title { get; set; }
}
[XmlRoot(ElementName = "Server")]
public class ServerLib
{
[XmlElement(ElementName = "Section")]
public List<SectionLite> Section { get; set; }
[XmlAttribute(AttributeName = "name")]
public string Name { get; set; }
[XmlAttribute(AttributeName = "address")]
public string Address { get; set; }
[XmlAttribute(AttributeName = "port")]
public string Port { get; set; }
[XmlAttribute(AttributeName = "version")]
public string Version { get; set; }
[XmlAttribute(AttributeName = "scheme")]
public string Scheme { get; set; }
[XmlAttribute(AttributeName = "host")]
public string Host { get; set; }
[XmlAttribute(AttributeName = "localAddresses")]
public string LocalAddresses { get; set; }
[XmlAttribute(AttributeName = "machineIdentifier")]
public string MachineIdentifier { get; set; }
[XmlAttribute(AttributeName = "createdAt")]
public string CreatedAt { get; set; }
[XmlAttribute(AttributeName = "updatedAt")]
public string UpdatedAt { get; set; }
[XmlAttribute(AttributeName = "owned")]
public string Owned { get; set; }
[XmlAttribute(AttributeName = "synced")]
public string Synced { get; set; }
}
[XmlRoot(ElementName = "MediaContainer")]
public class PlexLibrariesForMachineId
{
[XmlElement(ElementName = "Server")]
public ServerLib Server { get; set; }
[XmlAttribute(AttributeName = "friendlyName")]
public string FriendlyName { get; set; }
[XmlAttribute(AttributeName = "identifier")]
public string Identifier { get; set; }
[XmlAttribute(AttributeName = "machineIdentifier")]
public string MachineIdentifier { get; set; }
[XmlAttribute(AttributeName = "size")]
public string Size { get; set; }
}
}

View file

@ -16,14 +16,16 @@ namespace Ombi.Api.Plex
{ {
public class PlexApi : IPlexApi public class PlexApi : IPlexApi
{ {
public PlexApi(IApi api, ISettingsService<CustomizationSettings> settings) public PlexApi(IApi api, ISettingsService<CustomizationSettings> settings, ISettingsService<PlexSettings> p)
{ {
Api = api; Api = api;
_custom = settings; _custom = settings;
_plexSettings = p;
} }
private IApi Api { get; } private IApi Api { get; }
private readonly ISettingsService<CustomizationSettings> _custom; private readonly ISettingsService<CustomizationSettings> _custom;
private readonly ISettingsService<PlexSettings> _plexSettings;
private string _app; private string _app;
private string ApplicationName private string ApplicationName
@ -39,7 +41,18 @@ namespace Ombi.Api.Plex
} }
else else
{ {
_app = settings.ApplicationName; // Check for non-ascii characters (New .Net Core HTTPLib does not allow this)
var chars = settings.ApplicationName.ToCharArray();
var hasNonAscii = false;
foreach (var c in chars)
{
if (c > 128)
{
hasNonAscii = true;
}
}
_app = hasNonAscii ? "Ombi" : settings.ApplicationName;
} }
return _app; return _app;
@ -69,7 +82,7 @@ namespace Ombi.Api.Plex
}; };
var request = new Request(SignInUri, string.Empty, HttpMethod.Post); var request = new Request(SignInUri, string.Empty, HttpMethod.Post);
AddHeaders(request); await AddHeaders(request);
request.AddJsonBody(userModel); request.AddJsonBody(userModel);
var obj = await Api.Request<PlexAuthentication>(request); var obj = await Api.Request<PlexAuthentication>(request);
@ -80,14 +93,14 @@ namespace Ombi.Api.Plex
public async Task<PlexStatus> GetStatus(string authToken, string uri) public async Task<PlexStatus> GetStatus(string authToken, string uri)
{ {
var request = new Request(uri, string.Empty, HttpMethod.Get); var request = new Request(uri, string.Empty, HttpMethod.Get);
AddHeaders(request, authToken); await AddHeaders(request, authToken);
return await Api.Request<PlexStatus>(request); return await Api.Request<PlexStatus>(request);
} }
public async Task<PlexAccount> GetAccount(string authToken) public async Task<PlexAccount> GetAccount(string authToken)
{ {
var request = new Request(GetAccountUri, string.Empty, HttpMethod.Get); var request = new Request(GetAccountUri, string.Empty, HttpMethod.Get);
AddHeaders(request, authToken); await AddHeaders(request, authToken);
return await Api.Request<PlexAccount>(request); return await Api.Request<PlexAccount>(request);
} }
@ -95,7 +108,7 @@ namespace Ombi.Api.Plex
{ {
var request = new Request(ServerUri, string.Empty, HttpMethod.Get, ContentType.Xml); var request = new Request(ServerUri, string.Empty, HttpMethod.Get, ContentType.Xml);
AddHeaders(request, authToken); await AddHeaders(request, authToken);
return await Api.Request<PlexServer>(request); return await Api.Request<PlexServer>(request);
} }
@ -103,17 +116,24 @@ namespace Ombi.Api.Plex
public async Task<PlexContainer> GetLibrarySections(string authToken, string plexFullHost) public async Task<PlexContainer> GetLibrarySections(string authToken, string plexFullHost)
{ {
var request = new Request("library/sections", plexFullHost, HttpMethod.Get); var request = new Request("library/sections", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken); await AddHeaders(request, authToken);
return await Api.Request<PlexContainer>(request); return await Api.Request<PlexContainer>(request);
} }
public async Task<PlexContainer> GetLibrary(string authToken, string plexFullHost, string libraryId) public async Task<PlexContainer> GetLibrary(string authToken, string plexFullHost, string libraryId)
{ {
var request = new Request($"library/sections/{libraryId}/all", plexFullHost, HttpMethod.Get); var request = new Request($"library/sections/{libraryId}/all", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken); await AddHeaders(request, authToken);
return await Api.Request<PlexContainer>(request); return await Api.Request<PlexContainer>(request);
} }
public async Task<PlexLibrariesForMachineId> GetLibrariesForMachineId(string authToken, string machineId)
{
var request = new Request("", $"https://plex.tv/api/servers/{machineId}", HttpMethod.Get, ContentType.Xml);
await AddHeaders(request, authToken);
return await Api.Request<PlexLibrariesForMachineId>(request);
}
/// <summary> /// <summary>
// 192.168.1.69:32400/library/metadata/3662/allLeaves // 192.168.1.69:32400/library/metadata/3662/allLeaves
// The metadata ratingkey should be in the Cache // The metadata ratingkey should be in the Cache
@ -128,21 +148,21 @@ namespace Ombi.Api.Plex
public async Task<PlexMetadata> GetEpisodeMetaData(string authToken, string plexFullHost, int ratingKey) public async Task<PlexMetadata> GetEpisodeMetaData(string authToken, string plexFullHost, int ratingKey)
{ {
var request = new Request($"/library/metadata/{ratingKey}", plexFullHost, HttpMethod.Get); var request = new Request($"/library/metadata/{ratingKey}", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken); await AddHeaders(request, authToken);
return await Api.Request<PlexMetadata>(request); return await Api.Request<PlexMetadata>(request);
} }
public async Task<PlexMetadata> GetMetadata(string authToken, string plexFullHost, int itemId) public async Task<PlexMetadata> GetMetadata(string authToken, string plexFullHost, int itemId)
{ {
var request = new Request($"library/metadata/{itemId}", plexFullHost, HttpMethod.Get); var request = new Request($"library/metadata/{itemId}", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken); await AddHeaders(request, authToken);
return await Api.Request<PlexMetadata>(request); return await Api.Request<PlexMetadata>(request);
} }
public async Task<PlexMetadata> GetSeasons(string authToken, string plexFullHost, int ratingKey) public async Task<PlexMetadata> GetSeasons(string authToken, string plexFullHost, int ratingKey)
{ {
var request = new Request($"library/metadata/{ratingKey}/children", plexFullHost, HttpMethod.Get); var request = new Request($"library/metadata/{ratingKey}/children", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken); await AddHeaders(request, authToken);
return await Api.Request<PlexMetadata>(request); return await Api.Request<PlexMetadata>(request);
} }
@ -161,7 +181,7 @@ namespace Ombi.Api.Plex
request.AddQueryString("type", "4"); request.AddQueryString("type", "4");
AddLimitHeaders(request, start, retCount); AddLimitHeaders(request, start, retCount);
AddHeaders(request, authToken); await AddHeaders(request, authToken);
return await Api.Request<PlexContainer>(request); return await Api.Request<PlexContainer>(request);
} }
@ -175,7 +195,7 @@ namespace Ombi.Api.Plex
public async Task<PlexFriends> GetUsers(string authToken) public async Task<PlexFriends> GetUsers(string authToken)
{ {
var request = new Request(string.Empty, FriendsUri, HttpMethod.Get, ContentType.Xml); var request = new Request(string.Empty, FriendsUri, HttpMethod.Get, ContentType.Xml);
AddHeaders(request, authToken); await AddHeaders(request, authToken);
return await Api.Request<PlexFriends>(request); return await Api.Request<PlexFriends>(request);
} }
@ -183,43 +203,36 @@ namespace Ombi.Api.Plex
public async Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId) public async Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId)
{ {
var request = new Request($"library/sections/{sectionId}/recentlyAdded", uri, HttpMethod.Get); var request = new Request($"library/sections/{sectionId}/recentlyAdded", uri, HttpMethod.Get);
AddHeaders(request, authToken); await AddHeaders(request, authToken);
AddLimitHeaders(request, 0, 50); AddLimitHeaders(request, 0, 50);
return await Api.Request<PlexMetadata>(request); return await Api.Request<PlexMetadata>(request);
} }
public async Task<OAuthPin> CreatePin()
{
var request = new Request($"api/v2/pins", "https://plex.tv/", HttpMethod.Post);
request.AddQueryString("strong", "true");
AddHeaders(request);
return await Api.Request<OAuthPin>(request);
}
public async Task<OAuthPin> GetPin(int pinId) public async Task<OAuthPin> GetPin(int pinId)
{ {
var request = new Request($"api/v2/pins/{pinId}", "https://plex.tv/", HttpMethod.Get); var request = new Request($"api/v2/pins/{pinId}", "https://plex.tv/", HttpMethod.Get);
AddHeaders(request); await AddHeaders(request);
return await Api.Request<OAuthPin>(request); return await Api.Request<OAuthPin>(request);
} }
public Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard) public async Task<Uri> GetOAuthUrl(int pinId, string code, string applicationUrl)
{ {
var request = new Request("auth#", "https://app.plex.tv", HttpMethod.Get); var request = new Request("auth#", "https://app.plex.tv", HttpMethod.Get);
AddHeaders(request); await AddHeaders(request);
var forwardUrl = wizard
? new Request($"Wizard/OAuth/{pinId}", applicationUrl, HttpMethod.Get)
: new Request($"Login/OAuth/{pinId}", applicationUrl, HttpMethod.Get);
request.AddQueryString("forwardUrl", forwardUrl.FullUri.ToString());
request.AddQueryString("pinID", pinId.ToString()); request.AddQueryString("pinID", pinId.ToString());
request.AddQueryString("code", code); request.AddQueryString("code", code);
request.AddQueryString("context[device][product]", "Ombi"); request.AddQueryString("context[device][product]", ApplicationName);
request.AddQueryString("context[device][environment]", "bundled"); request.AddQueryString("context[device][environment]", "bundled");
request.AddQueryString("clientID", $"OmbiV3"); request.AddQueryString("context[device][layout]", "desktop");
request.AddQueryString("context[device][platform]", "Web");
request.AddQueryString("context[device][device]", "Ombi (Web)");
var s = await GetSettings();
await CheckInstallId(s);
request.AddQueryString("clientID", s.InstallId.ToString("N"));
if (request.FullUri.Fragment.Equals("#")) if (request.FullUri.Fragment.Equals("#"))
{ {
@ -233,26 +246,58 @@ namespace Ombi.Api.Plex
return request.FullUri; return request.FullUri;
} }
public async Task<PlexAddWrapper> AddUser(string emailAddress, string serverId, string authToken, int[] libs)
{
var request = new Request(string.Empty, $"https://plex.tv/api/servers/{serverId}/shared_servers", HttpMethod.Post, ContentType.Xml);
await AddHeaders(request, authToken);
request.AddJsonBody(new
{
server_id = serverId,
shared_server = new
{
library_section_ids = libs.Length > 0 ? libs : new int[]{},
invited_email = emailAddress
},
sharing_settings = new { }
});
var result = await Api.RequestContent(request);
try
{
var add = Api.DeserializeXml<PlexAdd>(result);
return new PlexAddWrapper{Add = add};
}
catch (InvalidOperationException)
{
var error = Api.DeserializeXml<AddUserError>(result);
return new PlexAddWrapper{Error = error};
}
}
/// <summary> /// <summary>
/// Adds the required headers and also the authorization header /// Adds the required headers and also the authorization header
/// </summary> /// </summary>
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="authToken"></param> /// <param name="authToken"></param>
private void AddHeaders(Request request, string authToken) private async Task AddHeaders(Request request, string authToken)
{ {
request.AddHeader("X-Plex-Token", authToken); request.AddHeader("X-Plex-Token", authToken);
AddHeaders(request); await AddHeaders(request);
} }
/// <summary> /// <summary>
/// Adds the main required headers to the Plex Request /// Adds the main required headers to the Plex Request
/// </summary> /// </summary>
/// <param name="request"></param> /// <param name="request"></param>
private void AddHeaders(Request request) private async Task AddHeaders(Request request)
{ {
request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3"); var s = await GetSettings();
await CheckInstallId(s);
request.AddHeader("X-Plex-Client-Identifier", s.InstallId.ToString("N"));
request.AddHeader("X-Plex-Product", ApplicationName); request.AddHeader("X-Plex-Product", ApplicationName);
request.AddHeader("X-Plex-Version", "3"); request.AddHeader("X-Plex-Version", "3");
request.AddHeader("X-Plex-Device", "Ombi (Web)");
request.AddHeader("X-Plex-Platform", "Web");
request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml"); request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml");
request.AddHeader("Accept", "application/json"); request.AddHeader("Accept", "application/json");
} }
@ -262,5 +307,19 @@ namespace Ombi.Api.Plex
request.AddHeader("X-Plex-Container-Start", from.ToString()); request.AddHeader("X-Plex-Container-Start", from.ToString());
request.AddHeader("X-Plex-Container-Size", to.ToString()); request.AddHeader("X-Plex-Container-Size", to.ToString());
} }
private async Task CheckInstallId(PlexSettings s)
{
if (s.InstallId == null || s.InstallId == Guid.Empty)
{
s.InstallId = Guid.NewGuid();
await _plexSettings.SaveSettingsAsync(s);
}
}
private PlexSettings _settings;
private async Task<PlexSettings> GetSettings()
{
return _settings ?? (_settings = await _plexSettings.GetSettingsAsync());
}
} }
} }

View file

@ -9,7 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.2" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -79,7 +79,7 @@ namespace Ombi.Api.Radarr
tmdbId = tmdbId, tmdbId = tmdbId,
qualityProfileId = qualityId, qualityProfileId = qualityId,
rootFolderPath = rootPath, rootFolderPath = rootPath,
titleSlug = title, titleSlug = title + year,
monitored = true, monitored = true,
year = year, year = year,
minimumAvailability = minimumAvailability minimumAvailability = minimumAvailability

View file

@ -11,7 +11,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.2" /> <PackageReference Include="Microsoft.Extensions.Options" Version="2.1.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -6,6 +6,30 @@ namespace Ombi.Api.Sonarr.Models
{ {
public class Episode public class Episode
{ {
public Episode()
{
}
public Episode(Episode ep)
{
seriesId = ep.seriesId;
episodeFileId = ep.episodeFileId;
seasonNumber = ep.seasonNumber;
episodeNumber = ep.episodeNumber;
title = ep.title;
airDate = ep.airDate;
airDateUtc = ep.airDateUtc;
overview = ep.overview;
hasFile = ep.hasFile;
monitored = ep.monitored;
unverifiedSceneNumbering = ep.unverifiedSceneNumbering;
id = ep.id;
absoluteEpisodeNumber = ep.absoluteEpisodeNumber;
sceneAbsoluteEpisodeNumber = ep.sceneAbsoluteEpisodeNumber;
sceneEpisodeNumber = ep.sceneEpisodeNumber;
sceneSeasonNumber = ep.sceneSeasonNumber;
}
public int seriesId { get; set; } public int seriesId { get; set; }
public int episodeFileId { get; set; } public int episodeFileId { get; set; }
public int seasonNumber { get; set; } public int seasonNumber { get; set; }
@ -27,6 +51,24 @@ namespace Ombi.Api.Sonarr.Models
public class Episodefile public class Episodefile
{ {
public Episodefile()
{
}
public Episodefile(Episodefile e)
{
seriesId = e.seriesId;
seasonNumber = e.seasonNumber;
relativePath = e.relativePath;
path = e.path;
size = e.size;
dateAdded = e.dateAdded;
sceneName = e.sceneName;
quality = new EpisodeQuality(e.quality);
qualityCutoffNotMet = e.qualityCutoffNotMet;
id = e.id;
}
public int seriesId { get; set; } public int seriesId { get; set; }
public int seasonNumber { get; set; } public int seasonNumber { get; set; }
public string relativePath { get; set; } public string relativePath { get; set; }
@ -41,12 +83,32 @@ namespace Ombi.Api.Sonarr.Models
public class EpisodeQuality public class EpisodeQuality
{ {
public EpisodeQuality()
{
}
public EpisodeQuality(EpisodeQuality e)
{
quality = new Quality(e.quality);
revision = new Revision(e.revision);
}
public Quality quality { get; set; } public Quality quality { get; set; }
public Revision revision { get; set; } public Revision revision { get; set; }
} }
public class Revision public class Revision
{ {
public Revision()
{
}
public Revision(Revision r)
{
version = r.version;
real = r.real;
}
public int version { get; set; } public int version { get; set; }
public int real { get; set; } public int real { get; set; }
} }

View file

@ -23,6 +23,7 @@ namespace Ombi.Api.Sonarr.Models
public string cleanTitle { get; set; } public string cleanTitle { get; set; }
public string imdbId { get; set; } public string imdbId { get; set; }
public string titleSlug { get; set; } public string titleSlug { get; set; }
public string seriesType { get; set; }
public int id { get; set; } public int id { get; set; }
public List<SonarrImage> images { get; set; } public List<SonarrImage> images { get; set; }

View file

@ -2,6 +2,16 @@ namespace Ombi.Api.Sonarr.Models
{ {
public class Quality public class Quality
{ {
public Quality()
{
}
public Quality(Quality q)
{
id = q.id;
name = q.name;
}
public int id { get; set; } public int id { get; set; }
public string name { get; set; } public string name { get; set; }
} }

View file

@ -80,14 +80,19 @@ namespace Ombi.Api
else else
{ {
// XML // XML
return DeserializeXml<T>(receivedString);
}
}
}
public T DeserializeXml<T>(string receivedString)
{
XmlSerializer serializer = new XmlSerializer(typeof(T)); XmlSerializer serializer = new XmlSerializer(typeof(T));
StringReader reader = new StringReader(receivedString); StringReader reader = new StringReader(receivedString);
var value = (T) serializer.Deserialize(reader); var value = (T) serializer.Deserialize(reader);
return value; return value;
} }
}
}
public async Task<string> RequestContent(Request request) public async Task<string> RequestContent(Request request)
{ {

View file

@ -7,5 +7,6 @@ namespace Ombi.Api
Task Request(Request request); Task Request(Request request);
Task<T> Request<T>(Request request); Task<T> Request<T>(Request request);
Task<string> RequestContent(Request request); Task<string> RequestContent(Request request);
T DeserializeXml<T>(string receivedString);
} }
} }

View file

@ -9,9 +9,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.2" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="Polly" Version="5.8.0" /> <PackageReference Include="Polly" Version="6.1.0" />
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" /> <PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
</ItemGroup> </ItemGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -9,7 +9,7 @@
<PackageReference Include="Nunit" Version="3.8.1" /> <PackageReference Include="Nunit" Version="3.8.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" /> <PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.7.2"></packagereference> <packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -29,7 +29,10 @@ namespace Ombi.Core.Tests.Rule.Search
{ {
ProviderId = "123" ProviderId = "123"
}); });
var search = new SearchMovieViewModel(); var search = new SearchMovieViewModel()
{
TheMovieDbId = "123",
};
var result = await Rule.Execute(search); var result = await Rule.Execute(search);
Assert.True(result.Success); Assert.True(result.Success);

View file

@ -20,12 +20,6 @@ namespace Ombi.Core.Authentication
private readonly IPlexApi _api; private readonly IPlexApi _api;
private readonly ISettingsService<CustomizationSettings> _customizationSettingsService; private readonly ISettingsService<CustomizationSettings> _customizationSettingsService;
public async Task<OAuthPin> RequestPin()
{
var pin = await _api.CreatePin();
return pin;
}
public async Task<string> GetAccessTokenFromPin(int pinId) public async Task<string> GetAccessTokenFromPin(int pinId)
{ {
var pin = await _api.GetPin(pinId); var pin = await _api.GetPin(pinId);
@ -34,19 +28,6 @@ namespace Ombi.Core.Authentication
return string.Empty; return string.Empty;
} }
if (pin.authToken.IsNullOrEmpty())
{
// Looks like we do not have a pin yet, we should retry a few times.
var retryCount = 0;
var retryMax = 5;
var retryWaitMs = 1000;
while (pin.authToken.IsNullOrEmpty() && retryCount < retryMax)
{
retryCount++;
await Task.Delay(retryWaitMs);
pin = await _api.GetPin(pinId);
}
}
return pin.authToken; return pin.authToken;
} }
@ -58,14 +39,14 @@ namespace Ombi.Core.Authentication
public async Task<Uri> GetOAuthUrl(int pinId, string code, string websiteAddress = null) public async Task<Uri> GetOAuthUrl(int pinId, string code, string websiteAddress = null)
{ {
var settings = await _customizationSettingsService.GetSettingsAsync(); var settings = await _customizationSettingsService.GetSettingsAsync();
var url = _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl, false); var url = await _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl);
return url; return url;
} }
public Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress) public async Task<Uri> GetWizardOAuthUrl(int pinId, string code, string websiteAddress)
{ {
var url = _api.GetOAuthUrl(pinId, code, websiteAddress, true); var url = await _api.GetOAuthUrl(pinId, code, websiteAddress);
return url; return url;
} }
} }

View file

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Ombi.Core.Engine
{
public interface IUserStatsEngine
{
Task<UserStatsSummary> GetSummary(SummaryRequest request);
}
}

View file

@ -15,13 +15,13 @@ namespace Ombi.Core.Engine.Interfaces
Task<RequestEngineResult> DenyChildRequest(int requestId); Task<RequestEngineResult> DenyChildRequest(int requestId);
Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type); Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type);
Task<IEnumerable<TvRequests>> SearchTvRequest(string search); Task<IEnumerable<TvRequests>> SearchTvRequest(string search);
Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvRequestTree(string search);
Task<TvRequests> UpdateTvRequest(TvRequests request); Task<TvRequests> UpdateTvRequest(TvRequests request);
Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> GetRequestsTreeNode(int count, int position);
Task<IEnumerable<ChildRequests>> GetAllChldren(int tvId); Task<IEnumerable<ChildRequests>> GetAllChldren(int tvId);
Task<ChildRequests> UpdateChildRequest(ChildRequests request); Task<ChildRequests> UpdateChildRequest(ChildRequests request);
Task RemoveTvChild(int requestId); Task RemoveTvChild(int requestId);
Task<RequestEngineResult> ApproveChildRequest(int id); Task<RequestEngineResult> ApproveChildRequest(int id);
Task<IEnumerable<TvRequests>> GetRequestsLite(); Task<IEnumerable<TvRequests>> GetRequestsLite();
Task UpdateQualityProfile(int requestId, int profileId);
Task UpdateRootPath(int requestId, int rootPath);
} }
} }

View file

@ -7,16 +7,10 @@ namespace Ombi.Core.Engine.Interfaces
public interface ITvSearchEngine public interface ITvSearchEngine
{ {
Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm); Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm);
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> SearchTreeNode(string searchTerm);
Task<TreeNode<SearchTvShowViewModel>> GetShowInformationTreeNode(int tvdbid);
Task<SearchTvShowViewModel> GetShowInformation(int tvdbid); Task<SearchTvShowViewModel> GetShowInformation(int tvdbid);
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTree();
Task<IEnumerable<SearchTvShowViewModel>> Popular(); Task<IEnumerable<SearchTvShowViewModel>> Popular();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticipatedTree();
Task<IEnumerable<SearchTvShowViewModel>> Anticipated(); Task<IEnumerable<SearchTvShowViewModel>> Anticipated();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatchesTree();
Task<IEnumerable<SearchTvShowViewModel>> MostWatches(); Task<IEnumerable<SearchTvShowViewModel>> MostWatches();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> TrendingTree();
Task<IEnumerable<SearchTvShowViewModel>> Trending(); Task<IEnumerable<SearchTvShowViewModel>> Trending();
} }
} }

View file

@ -452,6 +452,7 @@ namespace Ombi.Core.Engine
} }
request.Available = true; request.Available = true;
request.MarkedAsAvailable = DateTime.Now;
NotificationHelper.Notify(request, NotificationType.RequestAvailable); NotificationHelper.Notify(request, NotificationType.RequestAvailable);
await MovieRepository.Update(request); await MovieRepository.Update(request);

View file

@ -1,23 +0,0 @@
using System.Collections.Generic;
namespace Ombi.Core.Engine
{
public class TreeNode<T>
{
public string Label { get; set; }
public T Data { get; set; }
public List<TreeNode<T>> Children { get; set; }
public bool Leaf { get; set; }
public bool Expanded { get; set; }
}
public class TreeNode<T,U>
{
public string Label { get; set; }
public T Data { get; set; }
public List<TreeNode<U>> Children { get; set; }
public bool Leaf { get; set; }
public bool Expanded { get; set; }
}
}

View file

@ -143,7 +143,7 @@ namespace Ombi.Core.Engine
.Include(x => x.ChildRequests) .Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests) .ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes) .ThenInclude(x => x.Episodes)
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) .OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault())
.Skip(position).Take(count).ToListAsync(); .Skip(position).Take(count).ToListAsync();
// Filter out children // Filter out children
@ -156,8 +156,9 @@ namespace Ombi.Core.Engine
.Include(x => x.ChildRequests) .Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests) .ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes) .ThenInclude(x => x.Episodes)
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) .OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault())
.Skip(position).Take(count).ToListAsync(); .Skip(position).Take(count).ToListAsync();
} }
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
@ -171,24 +172,30 @@ namespace Ombi.Core.Engine
public async Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type) public async Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type)
{ {
var shouldHide = await HideFromOtherUsers(); var shouldHide = await HideFromOtherUsers();
List<TvRequests> allRequests; List<TvRequests> allRequests = null;
if (shouldHide.Hide) if (shouldHide.Hide)
{ {
allRequests = await TvRepository.GetLite(shouldHide.UserId) var tv = TvRepository.GetLite(shouldHide.UserId);
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) if (tv.Any() && tv.Select(x => x.ChildRequests).Any())
.Skip(position).Take(count).ToListAsync(); {
allRequests = await tv.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()).Skip(position).Take(count).ToListAsync();
}
// Filter out children // Filter out children
FilterChildren(allRequests, shouldHide); FilterChildren(allRequests, shouldHide);
} }
else else
{ {
allRequests = await TvRepository.GetLite() var tv = TvRepository.GetLite();
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) if (tv.Any() && tv.Select(x => x.ChildRequests).Any())
.Skip(position).Take(count).ToListAsync(); {
allRequests = await tv.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()).Skip(position).Take(count).ToListAsync();
}
}
if (allRequests == null)
{
return new RequestsViewModel<TvRequests>();
} }
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return new RequestsViewModel<TvRequests> return new RequestsViewModel<TvRequests>
@ -196,38 +203,6 @@ namespace Ombi.Core.Engine
Collection = allRequests Collection = allRequests
}; };
} }
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> GetRequestsTreeNode(int count, int position)
{
var shouldHide = await HideFromOtherUsers();
List<TvRequests> allRequests;
if (shouldHide.Hide)
{
allRequests = await TvRepository.Get(shouldHide.UserId)
.Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes)
.Where(x => x.ChildRequests.Any())
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
.Skip(position).Take(count).ToListAsync();
FilterChildren(allRequests, shouldHide);
}
else
{
allRequests = await TvRepository.Get()
.Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes)
.Where(x => x.ChildRequests.Any())
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
.Skip(position).Take(count).ToListAsync();
}
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return ParseIntoTreeNode(allRequests);
}
public async Task<IEnumerable<TvRequests>> GetRequests() public async Task<IEnumerable<TvRequests>> GetRequests()
{ {
var shouldHide = await HideFromOtherUsers(); var shouldHide = await HideFromOtherUsers();
@ -288,6 +263,10 @@ namespace Ombi.Core.Engine
private static void FilterChildren(IEnumerable<TvRequests> allRequests, HideResult shouldHide) private static void FilterChildren(IEnumerable<TvRequests> allRequests, HideResult shouldHide)
{ {
if (allRequests == null)
{
return;
}
// Filter out children // Filter out children
foreach (var t in allRequests) foreach (var t in allRequests)
{ {
@ -350,21 +329,22 @@ namespace Ombi.Core.Engine
return results; return results;
} }
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvRequestTree(string search) public async Task UpdateRootPath(int requestId, int rootPath)
{ {
var shouldHide = await HideFromOtherUsers(); var allRequests = TvRepository.Get();
IQueryable<TvRequests> allRequests; var results = await allRequests.FirstOrDefaultAsync(x => x.Id == requestId);
if (shouldHide.Hide) results.RootFolder = rootPath;
{
allRequests = TvRepository.Get(shouldHide.UserId); await TvRepository.Update(results);
} }
else
public async Task UpdateQualityProfile(int requestId, int profileId)
{ {
allRequests = TvRepository.Get(); var allRequests = TvRepository.Get();
} var results = await allRequests.FirstOrDefaultAsync(x => x.Id == requestId);
var results = await allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToListAsync(); results.QualityOverride = profileId;
results.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return ParseIntoTreeNode(results); await TvRepository.Update(results);
} }
public async Task<TvRequests> UpdateTvRequest(TvRequests request) public async Task<TvRequests> UpdateTvRequest(TvRequests request)
@ -516,6 +496,7 @@ namespace Ombi.Core.Engine
}; };
} }
request.Available = true; request.Available = true;
request.MarkedAsAvailable = DateTime.Now;
foreach (var season in request.SeasonRequests) foreach (var season in request.SeasonRequests)
{ {
foreach (var e in season.Episodes) foreach (var e in season.Episodes)
@ -585,28 +566,6 @@ namespace Ombi.Core.Engine
return await AfterRequest(model.ChildRequests.FirstOrDefault()); return await AfterRequest(model.ChildRequests.FirstOrDefault());
} }
private static List<TreeNode<TvRequests, List<ChildRequests>>> ParseIntoTreeNode(IEnumerable<TvRequests> result)
{
var node = new List<TreeNode<TvRequests, List<ChildRequests>>>();
foreach (var value in result)
{
node.Add(new TreeNode<TvRequests, List<ChildRequests>>
{
Data = value,
Children = new List<TreeNode<List<ChildRequests>>>
{
new TreeNode<List<ChildRequests>>
{
Data = SortEpisodes(value.ChildRequests),
Leaf = true
}
}
});
}
return node;
}
private static List<ChildRequests> SortEpisodes(List<ChildRequests> items) private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
{ {
foreach (var value in items) foreach (var value in items)

View file

@ -59,11 +59,6 @@ namespace Ombi.Core.Engine
return null; return null;
} }
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> SearchTreeNode(string searchTerm)
{
var result = await Search(searchTerm);
return result.Select(ParseIntoTreeNode).ToList();
}
public async Task<SearchTvShowViewModel> GetShowInformation(int tvdbid) public async Task<SearchTvShowViewModel> GetShowInformation(int tvdbid)
{ {
var show = await TvMazeApi.ShowLookupByTheTvDbId(tvdbid); var show = await TvMazeApi.ShowLookupByTheTvDbId(tvdbid);
@ -116,19 +111,6 @@ namespace Ombi.Core.Engine
return await ProcessResult(mapped); return await ProcessResult(mapped);
} }
public async Task<TreeNode<SearchTvShowViewModel>> GetShowInformationTreeNode(int tvdbid)
{
var result = await GetShowInformation(tvdbid);
return ParseIntoTreeNode(result);
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTree()
{
var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> Popular() public async Task<IEnumerable<SearchTvShowViewModel>> Popular()
{ {
var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12)); var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12));
@ -136,12 +118,6 @@ namespace Ombi.Core.Engine
return processed; return processed;
} }
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticipatedTree()
{
var result = await Cache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> Anticipated() public async Task<IEnumerable<SearchTvShowViewModel>> Anticipated()
{ {
@ -150,12 +126,6 @@ namespace Ombi.Core.Engine
return processed; return processed;
} }
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatchesTree()
{
var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches() public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches()
{ {
var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12)); var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12));
@ -163,13 +133,6 @@ namespace Ombi.Core.Engine
return processed; return processed;
} }
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> TrendingTree()
{
var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> Trending() public async Task<IEnumerable<SearchTvShowViewModel>> Trending()
{ {
var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12)); var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12));
@ -177,22 +140,6 @@ namespace Ombi.Core.Engine
return processed; return processed;
} }
private static TreeNode<SearchTvShowViewModel> ParseIntoTreeNode(SearchTvShowViewModel result)
{
return new TreeNode<SearchTvShowViewModel>
{
Data = result,
Children = new List<TreeNode<SearchTvShowViewModel>>
{
new TreeNode<SearchTvShowViewModel>
{
Data = result, Leaf = true
}
},
Leaf = false
};
}
private async Task<IEnumerable<SearchTvShowViewModel>> ProcessResults<T>(IEnumerable<T> items) private async Task<IEnumerable<SearchTvShowViewModel>> ProcessResults<T>(IEnumerable<T> items)
{ {
var retVal = new List<SearchTvShowViewModel>(); var retVal = new List<SearchTvShowViewModel>();

View file

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Store.Entities;
using Ombi.Store.Repository.Requests;
namespace Ombi.Core.Engine
{
public class UserStatsEngine : IUserStatsEngine
{
public UserStatsEngine(OmbiUserManager um, IMovieRequestRepository movieRequest, ITvRequestRepository tvRequest)
{
_userManager = um;
_movieRequest = movieRequest;
_tvRequest = tvRequest;
}
private readonly OmbiUserManager _userManager;
private readonly IMovieRequestRepository _movieRequest;
private readonly ITvRequestRepository _tvRequest;
public async Task<UserStatsSummary> GetSummary(SummaryRequest request)
{
// get all movie requests
var movies = _movieRequest.GetWithUser();
var filteredMovies = movies.Where(x => x.RequestedDate >= request.From && x.RequestedDate <= request.To);
var tv = _tvRequest.GetLite();
var children = tv.SelectMany(x =>
x.ChildRequests.Where(c => c.RequestedDate >= request.From && c.RequestedDate <= request.To));
var moviesCount = filteredMovies.CountAsync();
var childrenCount = children.CountAsync();
var availableMovies =
movies.Select(x => x.MarkedAsAvailable >= request.From && x.MarkedAsAvailable <= request.To).CountAsync();
var availableChildren = tv.SelectMany(x =>
x.ChildRequests.Where(c => c.MarkedAsAvailable >= request.From && c.MarkedAsAvailable <= request.To)).CountAsync();
var userMovie = filteredMovies.GroupBy(x => x.RequestedUserId).OrderBy(x => x.Key).FirstOrDefaultAsync();
var userTv = children.GroupBy(x => x.RequestedUserId).OrderBy(x => x.Key).FirstOrDefaultAsync();
return new UserStatsSummary
{
TotalMovieRequests = await moviesCount,
TotalTvRequests = await childrenCount,
CompletedRequestsTv = await availableChildren,
CompletedRequestsMovies = await availableMovies,
MostRequestedUserMovie = (await userMovie).FirstOrDefault().RequestedUser,
MostRequestedUserTv = (await userTv).FirstOrDefault().RequestedUser,
};
}
}
public class SummaryRequest
{
public DateTime From { get; set; }
public DateTime To { get; set; }
}
public class UserStatsSummary
{
public int TotalRequests => TotalTvRequests + TotalMovieRequests;
public int TotalMovieRequests { get; set; }
public int TotalTvRequests { get; set; }
public int TotalIssues { get; set; }
public int CompletedRequestsMovies { get; set; }
public int CompletedRequestsTv { get; set; }
public int CompletedRequests => CompletedRequestsMovies + CompletedRequestsTv;
public OmbiUser MostRequestedUserMovie { get; set; }
public OmbiUser MostRequestedUserTv { get; set; }
}
}

View file

@ -1,16 +1,14 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Api.Plex.Models; using Ombi.Api.Plex.Models;
using Ombi.Api.Plex.Models.OAuth;
namespace Ombi.Core.Authentication namespace Ombi.Core.Authentication
{ {
public interface IPlexOAuthManager public interface IPlexOAuthManager
{ {
Task<string> GetAccessTokenFromPin(int pinId); Task<string> GetAccessTokenFromPin(int pinId);
Task<OAuthPin> RequestPin();
Task<Uri> GetOAuthUrl(int pinId, string code, string websiteAddress = null); Task<Uri> GetOAuthUrl(int pinId, string code, string websiteAddress = null);
Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress); Task<Uri> GetWizardOAuthUrl(int pinId, string code, string websiteAddress);
Task<PlexAccount> GetAccount(string accessToken); Task<PlexAccount> GetAccount(string accessToken);
} }
} }

View file

@ -12,9 +12,9 @@
<PackageReference Include="AutoMapper" Version="6.1.1" /> <PackageReference Include="AutoMapper" Version="6.1.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.2.0" /> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.2.0" />
<PackageReference Include="Hangfire" Version="1.6.19" /> <PackageReference Include="Hangfire" Version="1.6.19" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.5" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="2.0.0-preview1-final" />
<PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" /> <PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" /> <PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />

View file

@ -1,4 +1,6 @@
using System.Linq; using System;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search; using Ombi.Core.Models.Search;
@ -59,10 +61,19 @@ namespace Ombi.Core.Rule.Rules.Search
{ {
EmbyEpisode epExists = null; EmbyEpisode epExists = null;
epExists = await allEpisodes.FirstOrDefaultAsync(x => if (item.HasImdb)
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && {
x.Series.ProviderId == item.ProviderId.ToString()); epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber
&& e.ImdbId == item.ImdbId);
} if (item.HasTvDb && epExists == null)
{
epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber
&& e.Series.TvDbId == item.TvDbId);
} if (item.HasTheMovieDb && epExists == null)
{
epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber
&& e.TheMovieDbId == item.TheMovieDbId);
}
if (epExists != null) if (epExists != null)
{ {

View file

@ -120,6 +120,7 @@ namespace Ombi.Core.Senders
int qualityToUse; int qualityToUse;
string rootFolderPath; string rootFolderPath;
string seriesType;
if (model.SeriesType == SeriesType.Anime) if (model.SeriesType == SeriesType.Anime)
{ {
@ -128,6 +129,8 @@ namespace Ombi.Core.Senders
// TODO make this overrideable via the UI // TODO make this overrideable via the UI
rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder ?? int.Parse(s.RootPathAnime), s); rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder ?? int.Parse(s.RootPathAnime), s);
int.TryParse(s.QualityProfileAnime, out qualityToUse); int.TryParse(s.QualityProfileAnime, out qualityToUse);
seriesType = "anime";
} }
else else
{ {
@ -136,6 +139,7 @@ namespace Ombi.Core.Senders
// For some reason, if we haven't got one use the first root folder in Sonarr // For some reason, if we haven't got one use the first root folder in Sonarr
// TODO make this overrideable via the UI // TODO make this overrideable via the UI
rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder ?? int.Parse(s.RootPath), s); rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder ?? int.Parse(s.RootPath), s);
seriesType = "standard";
} }
if (model.ParentRequest.QualityOverride.HasValue) if (model.ParentRequest.QualityOverride.HasValue)
@ -163,27 +167,18 @@ namespace Ombi.Core.Senders
rootFolderPath = rootFolderPath, rootFolderPath = rootFolderPath,
qualityProfileId = qualityToUse, qualityProfileId = qualityToUse,
titleSlug = model.ParentRequest.Title, titleSlug = model.ParentRequest.Title,
seriesType = seriesType,
addOptions = new AddOptions addOptions = new AddOptions
{ {
ignoreEpisodesWithFiles = true, // There shouldn't be any episodes with files, this is a new season ignoreEpisodesWithFiles = false, // There shouldn't be any episodes with files, this is a new season
ignoreEpisodesWithoutFiles = true, // We want all missing ignoreEpisodesWithoutFiles = false, // We want all missing
searchForMissingEpisodes = false // we want dont want to search yet. We want to make sure everything is unmonitored/monitored correctly. searchForMissingEpisodes = false // we want dont want to search yet. We want to make sure everything is unmonitored/monitored correctly.
} }
}; };
// Montitor the correct seasons, // Montitor the correct seasons,
// If we have that season in the model then it's monitored! // If we have that season in the model then it's monitored!
var seasonsToAdd = new List<Season>(); var seasonsToAdd = GetSeasonsToCreate(model);
for (var i = 0; i < model.ParentRequest.TotalSeasons + 1; i++)
{
var index = i;
var season = new Season
{
seasonNumber = i,
monitored = model.SeasonRequests.Any(x => x.SeasonNumber == index && x.SeasonNumber != 0)
};
seasonsToAdd.Add(season);
}
newSeries.seasons = seasonsToAdd; newSeries.seasons = seasonsToAdd;
var result = await SonarrApi.AddSeries(newSeries, s.ApiKey, s.FullUri); var result = await SonarrApi.AddSeries(newSeries, s.ApiKey, s.FullUri);
existingSeries = await SonarrApi.GetSeriesById(result.id, s.ApiKey, s.FullUri); existingSeries = await SonarrApi.GetSeriesById(result.id, s.ApiKey, s.FullUri);
@ -237,7 +232,7 @@ namespace Ombi.Core.Senders
{ {
var sonarrEp = sonarrEpList.FirstOrDefault(x => var sonarrEp = sonarrEpList.FirstOrDefault(x =>
x.episodeNumber == ep.EpisodeNumber && x.seasonNumber == req.SeasonNumber); x.episodeNumber == ep.EpisodeNumber && x.seasonNumber == req.SeasonNumber);
if (sonarrEp != null) if (sonarrEp != null && !sonarrEp.monitored)
{ {
sonarrEp.monitored = true; sonarrEp.monitored = true;
episodesToUpdate.Add(sonarrEp); episodesToUpdate.Add(sonarrEp);
@ -245,22 +240,64 @@ namespace Ombi.Core.Senders
} }
} }
var seriesChanges = false; var seriesChanges = false;
foreach (var season in model.SeasonRequests) foreach (var season in model.SeasonRequests)
{ {
var sonarrSeason = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber); var sonarrSeason = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber);
var sonarrEpCount = sonarrSeason.Count(); var sonarrEpCount = sonarrSeason.Count();
var ourRequestCount = season.Episodes.Count; var ourRequestCount = season.Episodes.Count;
var existingSeason =
result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber);
if (existingSeason == null)
{
Logger.LogError("There was no season numer {0} in Sonarr for title {1}", season.SeasonNumber, model.ParentRequest.Title);
continue;
}
if (sonarrEpCount == ourRequestCount) if (sonarrEpCount == ourRequestCount)
{ {
// We have the same amount of requests as all of the episodes in the season. // We have the same amount of requests as all of the episodes in the season.
var existingSeason =
result.seasons.First(x => x.seasonNumber == season.SeasonNumber); if (!existingSeason.monitored)
{
existingSeason.monitored = true; existingSeason.monitored = true;
seriesChanges = true; seriesChanges = true;
} }
}
else else
{ {
// Make sure this season is set to monitored
if (!existingSeason.monitored)
{
// We need to monitor it, problem being is all episodes will now be monitored
// So we need to monior the series but unmonitor every episode
// Except the episodes that are already monitored before we update the series (we do not want to unmonitor episodes that are monitored beforehand)
existingSeason.monitored = true;
var sea = result.seasons.FirstOrDefault(x => x.seasonNumber == existingSeason.seasonNumber);
sea.monitored = true;
//var previouslyMonitoredEpisodes = sonarrEpList.Where(x =>
// x.seasonNumber == existingSeason.seasonNumber && x.monitored).Select(x => x.episodeNumber).ToList(); // We probably don't actually care about this
result = await SonarrApi.UpdateSeries(result, s.ApiKey, s.FullUri);
var epToUnmonitor = new List<Episode>();
var newEpList = sonarrEpList.ConvertAll(ep => new Episode(ep)); // Clone it so we don't modify the orignal member
foreach (var ep in newEpList.Where(x => x.seasonNumber == existingSeason.seasonNumber).ToList())
{
//if (previouslyMonitoredEpisodes.Contains(ep.episodeNumber))
//{
// // This was previously monitored.
// continue;
//}
ep.monitored = false;
epToUnmonitor.Add(ep);
}
foreach (var epToUpdate in epToUnmonitor)
{
await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri);
}
}
// Now update the episodes that need updating // Now update the episodes that need updating
foreach (var epToUpdate in episodesToUpdate.Where(x => x.seasonNumber == season.SeasonNumber)) foreach (var epToUpdate in episodesToUpdate.Where(x => x.seasonNumber == season.SeasonNumber))
{ {
@ -280,6 +317,24 @@ namespace Ombi.Core.Senders
} }
} }
private static List<Season> GetSeasonsToCreate(ChildRequests model)
{
// Let's get a list of seasons just incase we need to change it
var seasonsToUpdate = new List<Season>();
for (var i = 0; i < model.ParentRequest.TotalSeasons + 1; i++)
{
var index = i;
var sea = new Season
{
seasonNumber = i,
monitored = model.SeasonRequests.Any(x => x.SeasonNumber == index && x.SeasonNumber != 0)
};
seasonsToUpdate.Add(sea);
}
return seasonsToUpdate;
}
private async Task<bool> SendToSickRage(ChildRequests model, SickRageSettings settings, string qualityId = null) private async Task<bool> SendToSickRage(ChildRequests model, SickRageSettings settings, string qualityId = null)
{ {
var tvdbid = model.ParentRequest.TvDbId; var tvdbid = model.ParentRequest.TvDbId;

View file

@ -79,6 +79,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<ITvRequestEngine, TvRequestEngine>(); services.AddTransient<ITvRequestEngine, TvRequestEngine>();
services.AddTransient<ITvSearchEngine, TvSearchEngine>(); services.AddTransient<ITvSearchEngine, TvSearchEngine>();
services.AddTransient<IRuleEvaluator, RuleEvaluator>(); services.AddTransient<IRuleEvaluator, RuleEvaluator>();
services.AddTransient<IUserStatsEngine, UserStatsEngine>();
services.AddTransient<IMovieSender, MovieSender>(); services.AddTransient<IMovieSender, MovieSender>();
services.AddTransient<IRecentlyAddedEngine, RecentlyAddedEngine>(); services.AddTransient<IRecentlyAddedEngine, RecentlyAddedEngine>();
services.AddTransient<ITvSender, TvSender>(); services.AddTransient<ITvSender, TvSender>();

View file

@ -9,9 +9,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -10,7 +10,7 @@ namespace Ombi.Helpers
public static string GetEmbyMediaUrl(string mediaId) public static string GetEmbyMediaUrl(string mediaId)
{ {
var url = var url =
$"http://app.emby.media/itemdetails.html?id={mediaId}"; $"http://app.emby.media/#!/itemdetails.html?id={mediaId}";
return url; return url;
} }
} }

View file

@ -10,8 +10,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="EasyCrypto" Version="3.3.2" /> <PackageReference Include="EasyCrypto" Version="3.3.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.0.2" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.2" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="Nito.AsyncEx" Version="5.0.0-pre-05" /> <PackageReference Include="Nito.AsyncEx" Version="5.0.0-pre-05" />
<PackageReference Include="System.Security.Claims" Version="4.3.0" /> <PackageReference Include="System.Security.Claims" Version="4.3.0" />

View file

@ -183,13 +183,15 @@
<tr> <tr>
<td> <td>
{@RECENTLYADDED} {@RECENTLYADDED}
</td>
</tr>
<!-- END MAIN CONTENT AREA --> <!-- END MAIN CONTENT AREA -->
</table> </table>
<!-- START FOOTER --> <!-- START FOOTER -->
<div class="footer" style="clear: both; padding-top: 10px; text-align: center; width: 100%;"> <div class="footer" style="clear: both; padding-top: 10px; text-align: center; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%"> <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
<tr> <tr>
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-top: 10px; padding-bottom: 10px; font-size: 12px; color: #999999; text-align: center;" valign="top" align="center"> <td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-top: 10px; padding-bottom: 10px; font-size: 12px; color: #999999; text-align: center;" valign="top" align="center">
Powered by <a href="https://github.com/tidusjar/Ombi" style="color: #999999; font-size: 12px; text-align: center; text-decoration: underline;">Ombi</a> Powered by <a href="https://github.com/tidusjar/Ombi" style="color: #999999; font-size: 12px; text-align: center; text-decoration: underline;">Ombi</a>

View file

@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Nunit" Version="3.8.1" /> <PackageReference Include="Nunit" Version="3.8.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" /> <PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.7.2"></packagereference> <packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
<PackageReference Include="Moq" Version="4.7.99" /> <PackageReference Include="Moq" Version="4.7.99" />
</ItemGroup> </ItemGroup>

View file

@ -57,7 +57,7 @@ namespace Ombi.Notifications.Agents
// Get admin devices // Get admin devices
var playerIds = await GetAdmins(NotificationType.NewRequest); var playerIds = await GetAdmins(NotificationType.NewRequest);
await Send(playerIds, notification, settings); await Send(playerIds, notification, settings, model, true);
} }
protected override async Task NewIssue(NotificationOptions model, MobileNotificationSettings settings) protected override async Task NewIssue(NotificationOptions model, MobileNotificationSettings settings)
@ -75,7 +75,7 @@ namespace Ombi.Notifications.Agents
// Get admin devices // Get admin devices
var playerIds = await GetAdmins(NotificationType.Issue); var playerIds = await GetAdmins(NotificationType.Issue);
await Send(playerIds, notification, settings); await Send(playerIds, notification, settings, model);
} }
protected override async Task IssueComment(NotificationOptions model, MobileNotificationSettings settings) protected override async Task IssueComment(NotificationOptions model, MobileNotificationSettings settings)
@ -97,13 +97,13 @@ namespace Ombi.Notifications.Agents
{ {
// Send to user // Send to user
var playerIds = GetUsers(model, NotificationType.IssueComment); var playerIds = GetUsers(model, NotificationType.IssueComment);
await Send(playerIds, notification, settings); await Send(playerIds, notification, settings, model);
} }
else else
{ {
// Send to admin // Send to admin
var playerIds = await GetAdmins(NotificationType.IssueComment); var playerIds = await GetAdmins(NotificationType.IssueComment);
await Send(playerIds, notification, settings); await Send(playerIds, notification, settings, model);
} }
} }
} }
@ -124,7 +124,7 @@ namespace Ombi.Notifications.Agents
// Send to user // Send to user
var playerIds = GetUsers(model, NotificationType.IssueResolved); var playerIds = GetUsers(model, NotificationType.IssueResolved);
await Send(playerIds, notification, settings); await Send(playerIds, notification, settings, model);
} }
@ -149,7 +149,7 @@ namespace Ombi.Notifications.Agents
}; };
// Get admin devices // Get admin devices
var playerIds = await GetAdmins(NotificationType.Test); var playerIds = await GetAdmins(NotificationType.Test);
await Send(playerIds, notification, settings); await Send(playerIds, notification, settings, model);
} }
protected override async Task RequestDeclined(NotificationOptions model, MobileNotificationSettings settings) protected override async Task RequestDeclined(NotificationOptions model, MobileNotificationSettings settings)
@ -168,7 +168,7 @@ namespace Ombi.Notifications.Agents
// Send to user // Send to user
var playerIds = GetUsers(model, NotificationType.RequestDeclined); var playerIds = GetUsers(model, NotificationType.RequestDeclined);
await AddSubscribedUsers(playerIds); await AddSubscribedUsers(playerIds);
await Send(playerIds, notification, settings); await Send(playerIds, notification, settings, model);
} }
protected override async Task RequestApproved(NotificationOptions model, MobileNotificationSettings settings) protected override async Task RequestApproved(NotificationOptions model, MobileNotificationSettings settings)
@ -188,7 +188,7 @@ namespace Ombi.Notifications.Agents
var playerIds = GetUsers(model, NotificationType.RequestApproved); var playerIds = GetUsers(model, NotificationType.RequestApproved);
await AddSubscribedUsers(playerIds); await AddSubscribedUsers(playerIds);
await Send(playerIds, notification, settings); await Send(playerIds, notification, settings, model);
} }
protected override async Task AvailableRequest(NotificationOptions model, MobileNotificationSettings settings) protected override async Task AvailableRequest(NotificationOptions model, MobileNotificationSettings settings)
@ -207,20 +207,20 @@ namespace Ombi.Notifications.Agents
var playerIds = GetUsers(model, NotificationType.RequestAvailable); var playerIds = GetUsers(model, NotificationType.RequestAvailable);
await AddSubscribedUsers(playerIds); await AddSubscribedUsers(playerIds);
await Send(playerIds, notification, settings); await Send(playerIds, notification, settings, model);
} }
protected override Task Send(NotificationMessage model, MobileNotificationSettings settings) protected override Task Send(NotificationMessage model, MobileNotificationSettings settings)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
protected async Task Send(List<string> playerIds, NotificationMessage model, MobileNotificationSettings settings) protected async Task Send(List<string> playerIds, NotificationMessage model, MobileNotificationSettings settings, NotificationOptions requestModel, bool isAdminNotification = false)
{ {
if (playerIds == null || !playerIds.Any()) if (playerIds == null || !playerIds.Any())
{ {
return; return;
} }
var response = await _api.PushNotification(playerIds, model.Message); var response = await _api.PushNotification(playerIds, model.Message, isAdminNotification, requestModel.RequestId, (int)requestModel.RequestType);
_logger.LogDebug("Sent message to {0} recipients with message id {1}", response.recipients, response.id); _logger.LogDebug("Sent message to {0} recipients with message id {1}", response.recipients, response.id);
} }
@ -239,7 +239,7 @@ namespace Ombi.Notifications.Agents
} }
var playerIds = user.NotificationUserIds.Select(x => x.PlayerId).ToList(); var playerIds = user.NotificationUserIds.Select(x => x.PlayerId).ToList();
await Send(playerIds, notification, settings); await Send(playerIds, notification, settings, model);
} }
private async Task<List<string>> GetAdmins(NotificationType type) private async Task<List<string>> GetAdmins(NotificationType type)

View file

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Humanizer;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Notifications.Models; using Ombi.Notifications.Models;
using Ombi.Settings.Settings.Models; using Ombi.Settings.Settings.Models;
@ -39,7 +40,7 @@ namespace Ombi.Notifications
RequestedDate = req?.RequestedDate.ToString("D"); RequestedDate = req?.RequestedDate.ToString("D");
if (Type.IsNullOrEmpty()) if (Type.IsNullOrEmpty())
{ {
Type = req?.RequestType.ToString(); Type = req?.RequestType.Humanize();
} }
Overview = req?.Overview; Overview = req?.Overview;
Year = req?.ReleaseDate.Year.ToString(); Year = req?.ReleaseDate.Year.ToString();
@ -91,7 +92,7 @@ namespace Ombi.Notifications
RequestedDate = req?.RequestedDate.ToString("D"); RequestedDate = req?.RequestedDate.ToString("D");
if (Type.IsNullOrEmpty()) if (Type.IsNullOrEmpty())
{ {
Type = req?.RequestType.ToString(); Type = req?.RequestType.Humanize();
} }
Overview = req?.ParentRequest.Overview; Overview = req?.ParentRequest.Overview;
@ -161,7 +162,7 @@ namespace Ombi.Notifications
IssueSubject = opts.Substitutes.TryGetValue("IssueSubject", out val) ? val : string.Empty; IssueSubject = opts.Substitutes.TryGetValue("IssueSubject", out val) ? val : string.Empty;
NewIssueComment = opts.Substitutes.TryGetValue("NewIssueComment", out val) ? val : string.Empty; NewIssueComment = opts.Substitutes.TryGetValue("NewIssueComment", out val) ? val : string.Empty;
UserName = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty; UserName = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty;
Type = opts.Substitutes.TryGetValue("RequestType", out val) ? val : string.Empty; Type = opts.Substitutes.TryGetValue("RequestType", out val) ? val.Humanize() : string.Empty;
} }
// User Defined // User Defined

View file

@ -10,7 +10,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Ensure.That" Version="7.0.0-pre32" /> <PackageReference Include="Ensure.That" Version="7.0.0-pre32" />
<PackageReference Include="MailKit" Version="2.0.3" /> <PackageReference Include="MailKit" Version="2.0.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.3" /> <PackageReference Include="Microsoft.AspNetCore" Version="2.1.2" />
<PackageReference Include="Moq" Version="4.7.99" /> <PackageReference Include="Moq" Version="4.7.99" />
<PackageReference Include="Nunit" Version="3.10.1" /> <PackageReference Include="Nunit" Version="3.10.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.8.0" /> <PackageReference Include="NUnit.ConsoleRunner" Version="3.8.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.7.0"></packagereference> <packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -89,6 +89,7 @@ namespace Ombi.Schedule.Jobs.Emby
_log.LogInformation("We have found the request {0} on Emby, sending the notification", movie?.Title ?? string.Empty); _log.LogInformation("We have found the request {0} on Emby, sending the notification", movie?.Title ?? string.Empty);
movie.Available = true; movie.Available = true;
movie.MarkedAsAvailable = DateTime.Now;
if (movie.Available) if (movie.Available)
{ {
var recipient = movie.RequestedUser.Email.HasValue() ? movie.RequestedUser.Email : string.Empty; var recipient = movie.RequestedUser.Email.HasValue() ? movie.RequestedUser.Email : string.Empty;
@ -121,24 +122,53 @@ namespace Ombi.Schedule.Jobs.Emby
foreach (var child in tv) foreach (var child in tv)
{ {
IQueryable<EmbyEpisode> seriesEpisodes;
if (child.ParentRequest.TvDbId > 0) var useImdb = false;
var useTvDb = false;
if (child.ParentRequest.ImdbId.HasValue())
{ {
seriesEpisodes = embyEpisodes.Where(x => x.Series.TvDbId == child.ParentRequest.TvDbId.ToString()); useImdb = true;
} }
else if(child.ParentRequest.ImdbId.HasValue())
if (child.ParentRequest.TvDbId.ToString().HasValue())
{ {
seriesEpisodes = embyEpisodes.Where(x => x.Series.ImdbId == child.ParentRequest.ImdbId); useTvDb = true;
} }
else
var tvDbId = child.ParentRequest.TvDbId;
var imdbId = child.ParentRequest.ImdbId;
IQueryable<EmbyEpisode> seriesEpisodes = null;
if (useImdb)
{
seriesEpisodes = embyEpisodes.Where(x => x.Series.ImdbId == imdbId.ToString());
}
if (useTvDb && (seriesEpisodes == null || !seriesEpisodes.Any()))
{
seriesEpisodes = embyEpisodes.Where(x => x.Series.TvDbId == tvDbId.ToString());
}
if (seriesEpisodes == null)
{ {
continue; continue;
} }
if (!seriesEpisodes.Any())
{
// Let's try and match the series by name
seriesEpisodes = embyEpisodes.Where(x =>
x.Series.Title.Equals(child.Title, StringComparison.CurrentCultureIgnoreCase));
}
foreach (var season in child.SeasonRequests) foreach (var season in child.SeasonRequests)
{ {
foreach (var episode in season.Episodes) foreach (var episode in season.Episodes)
{ {
if (episode.Available)
{
continue;
}
var foundEp = await seriesEpisodes.FirstOrDefaultAsync( var foundEp = await seriesEpisodes.FirstOrDefaultAsync(
x => x.EpisodeNumber == episode.EpisodeNumber && x => x.EpisodeNumber == episode.EpisodeNumber &&
x.SeasonNumber == episode.Season.SeasonNumber); x.SeasonNumber == episode.Season.SeasonNumber);
@ -156,13 +186,14 @@ namespace Ombi.Schedule.Jobs.Emby
{ {
// We have fulfulled this request! // We have fulfulled this request!
child.Available = true; child.Available = true;
child.MarkedAsAvailable = DateTime.Now;
BackgroundJob.Enqueue(() => _notificationService.Publish(new NotificationOptions BackgroundJob.Enqueue(() => _notificationService.Publish(new NotificationOptions
{ {
DateTime = DateTime.Now, DateTime = DateTime.Now,
NotificationType = NotificationType.RequestAvailable, NotificationType = NotificationType.RequestAvailable,
RequestId = child.ParentRequestId, RequestId = child.Id,
RequestType = RequestType.TvShow, RequestType = RequestType.TvShow,
Recipient = child.RequestedUser.Email, Recipient = child.RequestedUser.Email
})); }));
} }
} }

View file

@ -68,29 +68,68 @@ namespace Ombi.Schedule.Jobs.Emby
if (!ValidateSettings(server)) if (!ValidateSettings(server))
return; return;
await _repo.ExecuteSql("DELETE FROM EmbyEpisode"); //await _repo.ExecuteSql("DELETE FROM EmbyEpisode");
await _repo.ExecuteSql("DELETE FROM EmbyContent"); //await _repo.ExecuteSql("DELETE FROM EmbyContent");
var movies = await _api.GetAllMovies(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
var totalCount = movies.TotalRecordCount;
var processed = 1;
var movies = await _api.GetAllMovies(server.ApiKey, server.AdministratorId, server.FullUri);
var mediaToAdd = new HashSet<EmbyContent>(); var mediaToAdd = new HashSet<EmbyContent>();
while (processed < totalCount)
{
foreach (var movie in movies.Items) foreach (var movie in movies.Items)
{ {
if (movie.Type.Equals("boxset", StringComparison.InvariantCultureIgnoreCase))
{
var movieInfo =
await _api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri);
foreach (var item in movieInfo.Items)
{
await ProcessMovies(item, mediaToAdd);
}
processed++;
}
else
{
processed++;
// Regular movie // Regular movie
await ProcessMovies(movie, mediaToAdd); await ProcessMovies(movie, mediaToAdd);
} }
// TV Time }
var tv = await _api.GetAllShows(server.ApiKey, server.AdministratorId, server.FullUri);
// Get the next batch
movies = await _api.GetAllMovies(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
await _repo.AddRange(mediaToAdd);
mediaToAdd.Clear();
}
// TV Time
var tv = await _api.GetAllShows(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
var totalTv = tv.TotalRecordCount;
processed = 1;
while (processed < totalTv)
{
foreach (var tvShow in tv.Items) foreach (var tvShow in tv.Items)
{ {
try
{
processed++;
if (string.IsNullOrEmpty(tvShow.ProviderIds?.Tvdb)) if (string.IsNullOrEmpty(tvShow.ProviderIds?.Tvdb))
{ {
Log.Error("Provider Id on tv {0} is null", tvShow.Name); _logger.LogInformation("Provider Id on tv {0} is null", tvShow.Name);
continue; continue;
} }
var existingTv = await _repo.GetByEmbyId(tvShow.Id); var existingTv = await _repo.GetByEmbyId(tvShow.Id);
if (existingTv == null) if (existingTv == null)
{
_logger.LogDebug("Adding new TV Show {0}", tvShow.Name);
mediaToAdd.Add(new EmbyContent mediaToAdd.Add(new EmbyContent
{ {
TvDbId = tvShow.ProviderIds?.Tvdb, TvDbId = tvShow.ProviderIds?.Tvdb,
@ -103,6 +142,23 @@ namespace Ombi.Schedule.Jobs.Emby
AddedAt = DateTime.UtcNow AddedAt = DateTime.UtcNow
}); });
} }
else
{
_logger.LogDebug("We already have TV Show {0}", tvShow.Name);
}
}
catch (Exception)
{
throw;
}
}
// Get the next batch
tv = await _api.GetAllShows(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
await _repo.AddRange(mediaToAdd);
mediaToAdd.Clear();
}
if (mediaToAdd.Any()) if (mediaToAdd.Any())
await _repo.AddRange(mediaToAdd); await _repo.AddRange(mediaToAdd);
@ -112,8 +168,10 @@ namespace Ombi.Schedule.Jobs.Emby
{ {
// Check if it exists // Check if it exists
var existingMovie = await _repo.GetByEmbyId(movieInfo.Id); var existingMovie = await _repo.GetByEmbyId(movieInfo.Id);
var alreadyGoingToAdd = content.Any(x => x.EmbyId == movieInfo.Id);
if (existingMovie == null) if (existingMovie == null && !alreadyGoingToAdd)
{
_logger.LogDebug("Adding new movie {0}", movieInfo.Name);
content.Add(new EmbyContent content.Add(new EmbyContent
{ {
ImdbId = movieInfo.ProviderIds.Imdb, ImdbId = movieInfo.ProviderIds.Imdb,
@ -125,6 +183,12 @@ namespace Ombi.Schedule.Jobs.Emby
AddedAt = DateTime.UtcNow, AddedAt = DateTime.UtcNow,
}); });
} }
else
{
// we have this
_logger.LogDebug("We already have movie {0}", movieInfo.Name);
}
}
private bool ValidateSettings(EmbyServers server) private bool ValidateSettings(EmbyServers server)
{ {

View file

@ -73,23 +73,32 @@ namespace Ombi.Schedule.Jobs.Emby
private async Task CacheEpisodes(EmbyServers server) private async Task CacheEpisodes(EmbyServers server)
{ {
var allEpisodes = await _api.GetAllEpisodes(server.ApiKey, server.AdministratorId, server.FullUri); var allEpisodes = await _api.GetAllEpisodes(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
var epToAdd = new List<EmbyEpisode>(); var total = allEpisodes.TotalRecordCount;
var processed = 1;
var epToAdd = new HashSet<EmbyEpisode>();
while (processed < total)
{
foreach (var ep in allEpisodes.Items) foreach (var ep in allEpisodes.Items)
{ {
processed++;
// Let's make sure we have the parent request, stop those pesky forign key errors, // Let's make sure we have the parent request, stop those pesky forign key errors,
// Damn me having data integrity // Damn me having data integrity
var parent = await _repo.GetByEmbyId(ep.SeriesId); var parent = await _repo.GetByEmbyId(ep.SeriesId);
if (parent == null) if (parent == null)
{ {
_logger.LogInformation("The episode {0} does not relate to a series, so we cannot save this", ep.Name); _logger.LogInformation("The episode {0} does not relate to a series, so we cannot save this",
ep.Name);
continue; continue;
} }
var existingEpisode = await _repo.GetEpisodeByEmbyId(ep.Id); var existingEpisode = await _repo.GetEpisodeByEmbyId(ep.Id);
if (existingEpisode == null) // Make sure it's not in the hashset too
var existingInList = epToAdd.Any(x => x.EmbyId == ep.Id);
if (existingEpisode == null && !existingInList)
{ {
_logger.LogDebug("Adding new episode {0} to parent {1}", ep.Name, ep.SeriesName);
// add it // add it
epToAdd.Add(new EmbyEpisode epToAdd.Add(new EmbyEpisode
{ {
@ -106,6 +115,11 @@ namespace Ombi.Schedule.Jobs.Emby
} }
} }
await _repo.AddRange(epToAdd);
epToAdd.Clear();
allEpisodes = await _api.GetAllEpisodes(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
}
if (epToAdd.Any()) if (epToAdd.Any())
{ {
await _repo.AddRange(epToAdd); await _repo.AddRange(epToAdd);

View file

@ -491,8 +491,6 @@ namespace Ombi.Schedule.Jobs.Ombi
int count = 0; int count = 0;
var orderedTv = series.OrderByDescending(x => x.AddedAt); var orderedTv = series.OrderByDescending(x => x.AddedAt);
foreach (var t in orderedTv) foreach (var t in orderedTv)
{
try
{ {
if (!t.HasTvDb) if (!t.HasTvDb)
{ {
@ -527,6 +525,9 @@ namespace Ombi.Schedule.Jobs.Ombi
{ {
continue; continue;
} }
try
{
var banner = info.image?.original; var banner = info.image?.original;
if (!string.IsNullOrEmpty(banner)) if (!string.IsNullOrEmpty(banner))
{ {
@ -675,8 +676,6 @@ namespace Ombi.Schedule.Jobs.Ombi
int count = 0; int count = 0;
var orderedTv = series.OrderByDescending(x => x.AddedAt); var orderedTv = series.OrderByDescending(x => x.AddedAt);
foreach (var t in orderedTv) foreach (var t in orderedTv)
{
try
{ {
if (!t.TvDbId.HasValue()) if (!t.TvDbId.HasValue())
{ {
@ -690,6 +689,8 @@ namespace Ombi.Schedule.Jobs.Ombi
continue; continue;
} }
try
{
var banner = info.image?.original; var banner = info.image?.original;
if (!string.IsNullOrEmpty(banner)) if (!string.IsNullOrEmpty(banner))
{ {

View file

@ -72,7 +72,7 @@ namespace Ombi.Schedule.Jobs.Ombi
Logger.LogDebug(LoggingEvents.Updater, "Starting Update job"); Logger.LogDebug(LoggingEvents.Updater, "Starting Update job");
var settings = await Settings.GetSettingsAsync(); var settings = await Settings.GetSettingsAsync();
if (!settings.AutoUpdateEnabled) if (!settings.AutoUpdateEnabled && !settings.TestMode)
{ {
Logger.LogDebug(LoggingEvents.Updater, "Auto update is not enabled"); Logger.LogDebug(LoggingEvents.Updater, "Auto update is not enabled");
return; return;
@ -83,7 +83,7 @@ namespace Ombi.Schedule.Jobs.Ombi
var productVersion = AssemblyHelper.GetRuntimeVersion(); var productVersion = AssemblyHelper.GetRuntimeVersion();
Logger.LogDebug(LoggingEvents.Updater, "Product Version {0}", productVersion); Logger.LogDebug(LoggingEvents.Updater, "Product Version {0}", productVersion);
var serverVersion = string.Empty;
try try
{ {
var productArray = GetVersion(); var productArray = GetVersion();
@ -96,13 +96,17 @@ namespace Ombi.Schedule.Jobs.Ombi
Logger.LogDebug(LoggingEvents.Updater, "Branch {0}", branch); Logger.LogDebug(LoggingEvents.Updater, "Branch {0}", branch);
Logger.LogDebug(LoggingEvents.Updater, "Looking for updates now"); Logger.LogDebug(LoggingEvents.Updater, "Looking for updates now");
//TODO this fails because the branch = featureupdater when it should be feature/updater
var updates = await Processor.Process(branch); var updates = await Processor.Process(branch);
Logger.LogDebug(LoggingEvents.Updater, "Updates: {0}", updates); Logger.LogDebug(LoggingEvents.Updater, "Updates: {0}", updates);
var serverVersion = updates.UpdateVersionString;
serverVersion = updates.UpdateVersionString;
Logger.LogDebug(LoggingEvents.Updater, "Service Version {0}", updates.UpdateVersionString); Logger.LogDebug(LoggingEvents.Updater, "Service Version {0}", updates.UpdateVersionString);
if (!serverVersion.Equals(version, StringComparison.CurrentCultureIgnoreCase))
if (!serverVersion.Equals(version, StringComparison.CurrentCultureIgnoreCase) || settings.TestMode)
{ {
// Let's download the correct zip // Let's download the correct zip
var desc = RuntimeInformation.OSDescription; var desc = RuntimeInformation.OSDescription;
@ -135,7 +139,8 @@ namespace Ombi.Schedule.Jobs.Ombi
if (process == Architecture.Arm) if (process == Architecture.Arm)
{ {
download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("arm.", CompareOptions.IgnoreCase)); download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("arm.", CompareOptions.IgnoreCase));
} else if (process == Architecture.Arm64) }
else if (process == Architecture.Arm64)
{ {
download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("arm64.", CompareOptions.IgnoreCase)); download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("arm64.", CompareOptions.IgnoreCase));
} }
@ -206,32 +211,34 @@ namespace Ombi.Schedule.Jobs.Ombi
updaterExtension = ".exe"; updaterExtension = ".exe";
} }
var updaterFile = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), var updaterFile = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),
"TempUpdate", $"Ombi.Updater{updaterExtension}"); "TempUpdate", "updater", $"Ombi.Updater{updaterExtension}");
// Make sure the file is an executable // Make sure the file is an executable
ExecLinuxCommand($"chmod +x {updaterFile}"); //ExecLinuxCommand($"chmod +x {updaterFile}");
// There must be an update // There must be an update
var start = new ProcessStartInfo var start = new ProcessStartInfo
{ {
UseShellExecute = true, UseShellExecute = false,
CreateNoWindow = false, // Ignored if UseShellExecute is set to true CreateNoWindow = true, // Ignored if UseShellExecute is set to true
FileName = updaterFile, FileName = updaterFile,
Arguments = GetArgs(settings), Arguments = GetArgs(settings),
WorkingDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "TempUpdate"), WorkingDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "TempUpdate"),
}; };
if (settings.Username.HasValue()) //if (settings.Username.HasValue())
//{
// start.UserName = settings.Username;
//}
//if (settings.Password.HasValue())
//{
// start.Password = settings.Password.ToSecureString();
//}
using (var proc = new Process { StartInfo = start })
{ {
start.UserName = settings.Username;
}
if (settings.Password.HasValue())
{
start.Password = settings.Password.ToSecureString();
}
var proc = new Process { StartInfo = start };
proc.Start(); proc.Start();
}
Logger.LogDebug(LoggingEvents.Updater, "Bye bye"); Logger.LogDebug(LoggingEvents.Updater, "Bye bye");
} }
@ -254,10 +261,10 @@ namespace Ombi.Schedule.Jobs.Ombi
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append($"--applicationPath \"{currentLocation}\" --processname \"{processName}\" "); sb.Append($"--applicationPath \"{currentLocation}\" --processname \"{processName}\" ");
if (settings.WindowsService) //if (settings.WindowsService)
{ //{
sb.Append($"--windowsServiceName \"{settings.WindowsServiceName}\" "); // sb.Append($"--windowsServiceName \"{settings.WindowsServiceName}\" ");
} //}
var sb2 = new StringBuilder(); var sb2 = new StringBuilder();
if (url?.Value.HasValue() ?? false) if (url?.Value.HasValue() ?? false)
{ {

View file

@ -8,7 +8,7 @@ namespace Ombi.Schedule.Jobs.Plex.Models
public IEnumerable<int> Content { get; set; } public IEnumerable<int> Content { get; set; }
public IEnumerable<int> Episodes { get; set; } public IEnumerable<int> Episodes { get; set; }
public bool HasProcessedContent => Content.Any(); public bool HasProcessedContent => Content?.Any() ?? false;
public bool HasProcessedEpisodes => Episodes.Any(); public bool HasProcessedEpisodes => Episodes?.Any() ?? false;
} }
} }

View file

@ -79,11 +79,16 @@ namespace Ombi.Schedule.Jobs.Plex
{ {
seriesEpisodes = plexEpisodes.Where(x => x.Series.ImdbId == imdbId.ToString()); seriesEpisodes = plexEpisodes.Where(x => x.Series.ImdbId == imdbId.ToString());
} }
if (useTvDb) if (useTvDb && (seriesEpisodes == null || !seriesEpisodes.Any()) )
{ {
seriesEpisodes = plexEpisodes.Where(x => x.Series.TvDbId == tvDbId.ToString()); seriesEpisodes = plexEpisodes.Where(x => x.Series.TvDbId == tvDbId.ToString());
} }
if (seriesEpisodes == null)
{
continue;
}
if (!seriesEpisodes.Any()) if (!seriesEpisodes.Any())
{ {
// Let's try and match the series by name // Let's try and match the series by name
@ -118,6 +123,7 @@ namespace Ombi.Schedule.Jobs.Plex
{ {
// We have fulfulled this request! // We have fulfulled this request!
child.Available = true; child.Available = true;
child.MarkedAsAvailable = DateTime.Now;
_backgroundJobClient.Enqueue(() => _notificationService.Publish(new NotificationOptions _backgroundJobClient.Enqueue(() => _notificationService.Publish(new NotificationOptions
{ {
DateTime = DateTime.Now, DateTime = DateTime.Now,
@ -158,6 +164,7 @@ namespace Ombi.Schedule.Jobs.Plex
} }
movie.Available = true; movie.Available = true;
movie.MarkedAsAvailable = DateTime.Now;
if (movie.Available) if (movie.Available)
{ {
_backgroundJobClient.Enqueue(() => _notificationService.Publish(new NotificationOptions _backgroundJobClient.Enqueue(() => _notificationService.Publish(new NotificationOptions

View file

@ -150,9 +150,9 @@ namespace Ombi.Schedule.Jobs.Plex
var retVal = new ProcessedContent(); var retVal = new ProcessedContent();
var contentProcessed = new Dictionary<int, int>(); var contentProcessed = new Dictionary<int, int>();
var episodesProcessed = new List<int>(); var episodesProcessed = new List<int>();
Logger.LogInformation("Getting all content from server {0}", servers.Name); Logger.LogDebug("Getting all content from server {0}", servers.Name);
var allContent = await GetAllContent(servers, recentlyAddedSearch); var allContent = await GetAllContent(servers, recentlyAddedSearch);
Logger.LogInformation("We found {0} items", allContent.Count); Logger.LogDebug("We found {0} items", allContent.Count);
// Let's now process this. // Let's now process this.
var contentToAdd = new HashSet<PlexServerContent>(); var contentToAdd = new HashSet<PlexServerContent>();
@ -163,7 +163,7 @@ namespace Ombi.Schedule.Jobs.Plex
{ {
if (content.viewGroup.Equals(PlexMediaType.Episode.ToString(), StringComparison.CurrentCultureIgnoreCase)) if (content.viewGroup.Equals(PlexMediaType.Episode.ToString(), StringComparison.CurrentCultureIgnoreCase))
{ {
Logger.LogInformation("Found some episodes, this must be a recently added sync"); Logger.LogDebug("Found some episodes, this must be a recently added sync");
var count = 0; var count = 0;
foreach (var epInfo in content.Metadata ?? new Metadata[]{}) foreach (var epInfo in content.Metadata ?? new Metadata[]{})
{ {
@ -208,7 +208,7 @@ namespace Ombi.Schedule.Jobs.Plex
if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)) if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase))
{ {
// Process Shows // Process Shows
Logger.LogInformation("Processing TV Shows"); Logger.LogDebug("Processing TV Shows");
var count = 0; var count = 0;
foreach (var show in content.Metadata ?? new Metadata[] { }) foreach (var show in content.Metadata ?? new Metadata[] { })
{ {
@ -237,7 +237,7 @@ namespace Ombi.Schedule.Jobs.Plex
} }
if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase)) if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase))
{ {
Logger.LogInformation("Processing Movies"); Logger.LogDebug("Processing Movies");
foreach (var movie in content?.Metadata ?? new Metadata[] { }) foreach (var movie in content?.Metadata ?? new Metadata[] { })
{ {
// Let's check if we have this movie // Let's check if we have this movie
@ -251,7 +251,7 @@ namespace Ombi.Schedule.Jobs.Plex
//var existing = await Repo.GetByKey(movie.ratingKey); //var existing = await Repo.GetByKey(movie.ratingKey);
if (existing != null) if (existing != null)
{ {
Logger.LogInformation("We already have movie {0}", movie.title); Logger.LogDebug("We already have movie {0}", movie.title);
continue; continue;
} }
@ -261,7 +261,7 @@ namespace Ombi.Schedule.Jobs.Plex
await Repo.Delete(hasSameKey); await Repo.Delete(hasSameKey);
} }
Logger.LogInformation("Adding movie {0}", movie.title); Logger.LogDebug("Adding movie {0}", movie.title);
var metaData = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri, var metaData = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
movie.ratingKey); movie.ratingKey);
var providerIds = PlexHelper.GetProviderIdFromPlexGuid(metaData.MediaContainer.Metadata var providerIds = PlexHelper.GetProviderIdFromPlexGuid(metaData.MediaContainer.Metadata
@ -381,6 +381,19 @@ namespace Ombi.Schedule.Jobs.Plex
if (existingContent != null) if (existingContent != null)
{ {
// Let's make sure that we have some sort of ID e.g. Imdbid for this,
// Looks like it's possible to not have an Id for a show
// I suspect we cached that show just as it was added to Plex.
if (!existingContent.HasImdb && !existingContent.HasTheMovieDb && !existingContent.HasTvDb)
{
var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
existingContent.Key);
GetProviderIds(showMetadata, existingContent);
await Repo.Update(existingContent);
}
// Just check the key // Just check the key
if (existingKey != null) if (existingKey != null)
{ {
@ -421,7 +434,7 @@ namespace Ombi.Schedule.Jobs.Plex
{ {
try try
{ {
Logger.LogInformation("We already have show {0} checking for new seasons", Logger.LogDebug("We already have show {0} checking for new seasons",
existingContent.Title); existingContent.Title);
// Ok so we have it, let's check if there are any new seasons // Ok so we have it, let's check if there are any new seasons
var itemAdded = false; var itemAdded = false;
@ -472,15 +485,12 @@ namespace Ombi.Schedule.Jobs.Plex
{ {
try try
{ {
Logger.LogInformation("New show {0}, so add it", show.title); Logger.LogDebug("New show {0}, so add it", show.title);
// Get the show metadata... This sucks since the `metadata` var contains all information about the show // Get the show metadata... This sucks since the `metadata` var contains all information about the show
// But it does not contain the `guid` property that we need to pull out thetvdb id... // But it does not contain the `guid` property that we need to pull out thetvdb id...
var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri, var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
show.ratingKey); show.ratingKey);
var providerIds =
PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault()
.guid);
var item = new PlexServerContent var item = new PlexServerContent
{ {
@ -492,20 +502,7 @@ namespace Ombi.Schedule.Jobs.Plex
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey), Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey),
Seasons = new List<PlexSeasonsContent>() Seasons = new List<PlexSeasonsContent>()
}; };
if (providerIds.Type == ProviderType.ImdbId) GetProviderIds(showMetadata, item);
{
item.ImdbId = providerIds.ImdbId;
}
if (providerIds.Type == ProviderType.TheMovieDbId)
{
item.TheMovieDbId = providerIds.TheMovieDb;
}
if (providerIds.Type == ProviderType.TvDbId)
{
item.TvDbId = providerIds.TheTvDb;
}
// Let's just double check to make sure we do not have it now we have some id's // Let's just double check to make sure we do not have it now we have some id's
var existingImdb = false; var existingImdb = false;
@ -547,6 +544,27 @@ namespace Ombi.Schedule.Jobs.Plex
} }
} }
private static void GetProviderIds(PlexMetadata showMetadata, PlexServerContent existingContent)
{
var providerIds =
PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault()
.guid);
if (providerIds.Type == ProviderType.ImdbId)
{
existingContent.ImdbId = providerIds.ImdbId;
}
if (providerIds.Type == ProviderType.TheMovieDbId)
{
existingContent.TheMovieDbId = providerIds.TheMovieDb;
}
if (providerIds.Type == ProviderType.TvDbId)
{
existingContent.TvDbId = providerIds.TheTvDb;
}
}
/// <summary> /// <summary>
/// Gets all the library sections. /// Gets all the library sections.
/// If the user has specified only certain libraries then we will only look for those /// If the user has specified only certain libraries then we will only look for those
@ -573,7 +591,7 @@ namespace Ombi.Schedule.Jobs.Plex
.Select(x => x.Key.ToString()).ToList(); .Select(x => x.Key.ToString()).ToList();
if (!keys.Contains(dir.key)) if (!keys.Contains(dir.key))
{ {
Logger.LogInformation("Lib {0} is not monitored, so skipping", dir.key); Logger.LogDebug("Lib {0} is not monitored, so skipping", dir.key);
// We are not monitoring this lib // We are not monitoring this lib
continue; continue;
} }

View file

@ -57,6 +57,10 @@ namespace Ombi.Schedule.Jobs.Sonarr
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrEpisodeCache"); await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrEpisodeCache");
foreach (var s in sonarrSeries) foreach (var s in sonarrSeries)
{ {
if (!s.monitored)
{
continue;
}
_log.LogDebug("Syncing series: {0}", s.title); _log.LogDebug("Syncing series: {0}", s.title);
var episodes = await _api.GetEpisodes(s.id, settings.ApiKey, settings.FullUri); var episodes = await _api.GetEpisodes(s.id, settings.ApiKey, settings.FullUri);
var monitoredEpisodes = episodes.Where(x => x.monitored || x.hasFile); var monitoredEpisodes = episodes.Where(x => x.monitored || x.hasFile);

View file

@ -9,7 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup> </ItemGroup>

View file

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using Ombi.Settings.Settings.Models.External; using Ombi.Settings.Settings.Models.External;
namespace Ombi.Core.Settings.Models.External namespace Ombi.Core.Settings.Models.External
@ -6,6 +7,10 @@ namespace Ombi.Core.Settings.Models.External
public sealed class PlexSettings : Ombi.Settings.Settings.Models.Settings public sealed class PlexSettings : Ombi.Settings.Settings.Models.Settings
{ {
public bool Enable { get; set; } public bool Enable { get; set; }
/// <summary>
/// This is the ClientId for OAuth
/// </summary>
public Guid InstallId { get; set; }
public List<PlexServers> Servers { get; set; } public List<PlexServers> Servers { get; set; }
} }

View file

@ -10,5 +10,6 @@
public string ScriptLocation { get; set; } public string ScriptLocation { get; set; }
public string WindowsServiceName { get; set; } public string WindowsServiceName { get; set; }
public bool WindowsService { get; set; } public bool WindowsService { get; set; }
public bool TestMode { get; set; }
} }
} }

View file

@ -183,7 +183,7 @@ namespace Ombi.Store.Context
notificationToAdd = new NotificationTemplates notificationToAdd = new NotificationTemplates
{ {
NotificationType = notificationType, NotificationType = notificationType,
Message = "Hello! You {Title} on {ApplicationName}! This is now available! :)", Message = "Hello! Your request for {Title} on {ApplicationName}! This is now available! :)",
Subject = "{ApplicationName}: {Title} is now available!", Subject = "{ApplicationName}: {Title} is now available!",
Agent = agent, Agent = agent,
Enabled = true, Enabled = true,

View file

@ -6,7 +6,7 @@ namespace Ombi.Store.Entities
{ {
public enum RequestType public enum RequestType
{ {
TvShow, TvShow = 0,
Movie Movie = 1
} }
} }

View file

@ -8,10 +8,13 @@ namespace Ombi.Store.Entities.Requests
{ {
public string Title { get; set; } public string Title { get; set; }
public bool Approved { get; set; } public bool Approved { get; set; }
public DateTime MarkedAsApproved { get; set; }
public DateTime RequestedDate { get; set; } public DateTime RequestedDate { get; set; }
public bool Available { get; set; } public bool Available { get; set; }
public DateTime? MarkedAsAvailable { get; set; }
public string RequestedUserId { get; set; } public string RequestedUserId { get; set; }
public bool? Denied { get; set; } public bool? Denied { get; set; }
public DateTime MarkedAsDenied { get; set; }
public string DeniedReason { get; set; } public string DeniedReason { get; set; }
public RequestType RequestType { get; set; } public RequestType RequestType { get; set; }

View file

@ -0,0 +1,981 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Ombi.Helpers;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using System;
namespace Ombi.Store.Migrations
{
[DbContext(typeof(OmbiContext))]
[Migration("20180703200952_EmbyUrlFix")]
partial class EmbyUrlFix
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.3-rtm-10026");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Type");
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("ApplicationConfiguration");
});
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AuditArea");
b.Property<int>("AuditType");
b.Property<DateTime>("DateTime");
b.Property<string>("Description");
b.Property<string>("User");
b.HasKey("Id");
b.ToTable("Audit");
});
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("CouchPotatoCache");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId")
.IsRequired();
b.Property<string>("ImdbId");
b.Property<string>("ProviderId");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId");
b.Property<int>("EpisodeNumber");
b.Property<string>("ImdbId");
b.Property<string>("ParentId");
b.Property<string>("ProviderId");
b.Property<int>("SeasonNumber");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Content");
b.Property<string>("SettingsName");
b.HasKey("Id");
b.ToTable("GlobalSettings");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Agent");
b.Property<bool>("Enabled");
b.Property<string>("Message");
b.Property<int>("NotificationType");
b.Property<string>("Subject");
b.HasKey("Id");
b.ToTable("NotificationTemplates");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("PlayerId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("NotificationUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("Alias");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<string>("EmbyConnectUserId");
b.Property<int?>("EpisodeRequestLimit");
b.Property<DateTime?>("LastLoggedIn");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<int?>("MovieRequestLimit");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("ProviderUserId");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserAccessToken");
b.Property<string>("UserName")
.HasMaxLength(256);
b.Property<int>("UserType");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("GrandparentKey");
b.Property<int>("Key");
b.Property<int>("ParentKey");
b.Property<int>("SeasonNumber");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ParentKey");
b.Property<int>("PlexContentId");
b.Property<int?>("PlexServerContentId");
b.Property<int>("SeasonKey");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("PlexServerContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("ImdbId");
b.Property<int>("Key");
b.Property<string>("Quality");
b.Property<string>("ReleaseYear");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("PlexServerContent");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("HasFile");
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<int>("ContentId");
b.Property<int>("ContentType");
b.Property<int?>("EpisodeNumber");
b.Property<int?>("SeasonNumber");
b.Property<int>("Type");
b.HasKey("Id");
b.ToTable("RecentlyAddedLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<int?>("IssueId");
b.Property<int>("ParentRequestId");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("SeriesType");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("ParentRequestId");
b.HasIndex("RequestedUserId");
b.ToTable("ChildRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("IssueCategory");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Comment");
b.Property<DateTime>("Date");
b.Property<int?>("IssuesId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("IssuesId");
b.HasIndex("UserId");
b.ToTable("IssueComments");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Description");
b.Property<int>("IssueCategoryId");
b.Property<int?>("IssueId");
b.Property<string>("ProviderId");
b.Property<int?>("RequestId");
b.Property<int>("RequestType");
b.Property<DateTime?>("ResovledDate");
b.Property<int>("Status");
b.Property<string>("Subject");
b.Property<string>("Title");
b.Property<string>("UserReportedId");
b.HasKey("Id");
b.HasIndex("IssueCategoryId");
b.HasIndex("IssueId");
b.HasIndex("UserReportedId");
b.ToTable("Issues");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<string>("Background");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<DateTime?>("DigitalReleaseDate");
b.Property<string>("ImdbId");
b.Property<int?>("IssueId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("RootPathOverride");
b.Property<string>("Status");
b.Property<int>("TheMovieDbId");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("RequestedUserId");
b.ToTable("MovieRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeCount");
b.Property<DateTime>("RequestDate");
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Background");
b.Property<string>("ImdbId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int?>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int?>("RootFolder");
b.Property<string>("Status");
b.Property<string>("Title");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("TvRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestSubscription");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<bool>("HasFile");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Token");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Tokens");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AirDate");
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<int>("EpisodeNumber");
b.Property<bool>("Requested");
b.Property<int>("SeasonId");
b.Property<string>("Title");
b.Property<string>("Url");
b.HasKey("Id");
b.HasIndex("SeasonId");
b.ToTable("EpisodeRequests");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ChildRequestId");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("ChildRequestId");
b.ToTable("SeasonRequests");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("EmbyId");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany("NotificationUserIds")
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series")
.WithMany("Episodes")
.HasForeignKey("GrandparentKey")
.HasPrincipalKey("Key")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent")
.WithMany("Seasons")
.HasForeignKey("PlexServerContentId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest")
.WithMany("ChildRequests")
.HasForeignKey("ParentRequestId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
.WithMany()
.HasForeignKey("RequestedUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues")
.WithMany("Comments")
.HasForeignKey("IssuesId");
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory")
.WithMany()
.HasForeignKey("IssueCategoryId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests")
.WithMany("Issues")
.HasForeignKey("IssueId");
b.HasOne("Ombi.Store.Entities.Requests.MovieRequests")
.WithMany("Issues")
.HasForeignKey("IssueId");
b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported")
.WithMany()
.HasForeignKey("UserReportedId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
.WithMany()
.HasForeignKey("RequestedUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season")
.WithMany("Episodes")
.HasForeignKey("SeasonId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest")
.WithMany("SeasonRequests")
.HasForeignKey("ChildRequestId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace Ombi.Store.Migrations
{
public partial class EmbyUrlFix : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
@"UPDATE EmbyContent SET Url = replace( Url, 'http://app.emby.media/itemdetails.html', 'http://app.emby.media/#!/itemdetails.html' ) WHERE Url LIKE 'http://app.emby.media/itemdetails.html%';");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View file

@ -0,0 +1,988 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Ombi.Store.Context;
namespace Ombi.Store.Migrations
{
[DbContext(typeof(OmbiContext))]
[Migration("20180730085903_UserStats")]
partial class UserStats
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Type");
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("ApplicationConfiguration");
});
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AuditArea");
b.Property<int>("AuditType");
b.Property<DateTime>("DateTime");
b.Property<string>("Description");
b.Property<string>("User");
b.HasKey("Id");
b.ToTable("Audit");
});
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("CouchPotatoCache");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId")
.IsRequired();
b.Property<string>("ImdbId");
b.Property<string>("ProviderId");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId");
b.Property<int>("EpisodeNumber");
b.Property<string>("ImdbId");
b.Property<string>("ParentId");
b.Property<string>("ProviderId");
b.Property<int>("SeasonNumber");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Content");
b.Property<string>("SettingsName");
b.HasKey("Id");
b.ToTable("GlobalSettings");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Agent");
b.Property<bool>("Enabled");
b.Property<string>("Message");
b.Property<int>("NotificationType");
b.Property<string>("Subject");
b.HasKey("Id");
b.ToTable("NotificationTemplates");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("PlayerId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("NotificationUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("Alias");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<string>("EmbyConnectUserId");
b.Property<int?>("EpisodeRequestLimit");
b.Property<DateTime?>("LastLoggedIn");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<int?>("MovieRequestLimit");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("ProviderUserId");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserAccessToken");
b.Property<string>("UserName")
.HasMaxLength(256);
b.Property<int>("UserType");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("GrandparentKey");
b.Property<int>("Key");
b.Property<int>("ParentKey");
b.Property<int>("SeasonNumber");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ParentKey");
b.Property<int>("PlexContentId");
b.Property<int?>("PlexServerContentId");
b.Property<int>("SeasonKey");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("PlexServerContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("ImdbId");
b.Property<int>("Key");
b.Property<string>("Quality");
b.Property<string>("ReleaseYear");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("PlexServerContent");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("HasFile");
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<int>("ContentId");
b.Property<int>("ContentType");
b.Property<int?>("EpisodeNumber");
b.Property<int?>("SeasonNumber");
b.Property<int>("Type");
b.HasKey("Id");
b.ToTable("RecentlyAddedLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<int?>("IssueId");
b.Property<DateTime>("MarkedAsApproved");
b.Property<DateTime?>("MarkedAsAvailable");
b.Property<DateTime>("MarkedAsDenied");
b.Property<int>("ParentRequestId");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("SeriesType");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("ParentRequestId");
b.HasIndex("RequestedUserId");
b.ToTable("ChildRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("IssueCategory");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Comment");
b.Property<DateTime>("Date");
b.Property<int?>("IssuesId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("IssuesId");
b.HasIndex("UserId");
b.ToTable("IssueComments");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Description");
b.Property<int>("IssueCategoryId");
b.Property<int?>("IssueId");
b.Property<string>("ProviderId");
b.Property<int?>("RequestId");
b.Property<int>("RequestType");
b.Property<DateTime?>("ResovledDate");
b.Property<int>("Status");
b.Property<string>("Subject");
b.Property<string>("Title");
b.Property<string>("UserReportedId");
b.HasKey("Id");
b.HasIndex("IssueCategoryId");
b.HasIndex("IssueId");
b.HasIndex("UserReportedId");
b.ToTable("Issues");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<string>("Background");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<DateTime?>("DigitalReleaseDate");
b.Property<string>("ImdbId");
b.Property<int?>("IssueId");
b.Property<DateTime>("MarkedAsApproved");
b.Property<DateTime?>("MarkedAsAvailable");
b.Property<DateTime>("MarkedAsDenied");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("RootPathOverride");
b.Property<string>("Status");
b.Property<int>("TheMovieDbId");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("RequestedUserId");
b.ToTable("MovieRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeCount");
b.Property<DateTime>("RequestDate");
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Background");
b.Property<string>("ImdbId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int?>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int?>("RootFolder");
b.Property<string>("Status");
b.Property<string>("Title");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("TvRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestSubscription");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<bool>("HasFile");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Token");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Tokens");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AirDate");
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<int>("EpisodeNumber");
b.Property<bool>("Requested");
b.Property<int>("SeasonId");
b.Property<string>("Title");
b.Property<string>("Url");
b.HasKey("Id");
b.HasIndex("SeasonId");
b.ToTable("EpisodeRequests");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ChildRequestId");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("ChildRequestId");
b.ToTable("SeasonRequests");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("EmbyId");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany("NotificationUserIds")
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series")
.WithMany("Episodes")
.HasForeignKey("GrandparentKey")
.HasPrincipalKey("Key")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent")
.WithMany("Seasons")
.HasForeignKey("PlexServerContentId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest")
.WithMany("ChildRequests")
.HasForeignKey("ParentRequestId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
.WithMany()
.HasForeignKey("RequestedUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues")
.WithMany("Comments")
.HasForeignKey("IssuesId");
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory")
.WithMany()
.HasForeignKey("IssueCategoryId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests")
.WithMany("Issues")
.HasForeignKey("IssueId");
b.HasOne("Ombi.Store.Entities.Requests.MovieRequests")
.WithMany("Issues")
.HasForeignKey("IssueId");
b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported")
.WithMany()
.HasForeignKey("UserReportedId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
.WithMany()
.HasForeignKey("RequestedUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season")
.WithMany("Episodes")
.HasForeignKey("SeasonId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest")
.WithMany("SeasonRequests")
.HasForeignKey("ChildRequestId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,72 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Ombi.Store.Migrations
{
public partial class UserStats : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsApproved",
table: "MovieRequests",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsAvailable",
table: "MovieRequests",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsDenied",
table: "MovieRequests",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsApproved",
table: "ChildRequests",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsAvailable",
table: "ChildRequests",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "MarkedAsDenied",
table: "ChildRequests",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "MarkedAsApproved",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "MarkedAsAvailable",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "MarkedAsDenied",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "MarkedAsApproved",
table: "ChildRequests");
migrationBuilder.DropColumn(
name: "MarkedAsAvailable",
table: "ChildRequests");
migrationBuilder.DropColumn(
name: "MarkedAsDenied",
table: "ChildRequests");
}
}
}

View file

@ -1,15 +1,9 @@
// <auto-generated /> // <auto-generated />
using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Ombi.Helpers;
using Ombi.Store.Context; using Ombi.Store.Context;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using System;
namespace Ombi.Store.Migrations namespace Ombi.Store.Migrations
{ {
@ -20,7 +14,7 @@ namespace Ombi.Store.Migrations
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "2.0.3-rtm-10026"); .HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{ {
@ -481,6 +475,12 @@ namespace Ombi.Store.Migrations
b.Property<int?>("IssueId"); b.Property<int?>("IssueId");
b.Property<DateTime>("MarkedAsApproved");
b.Property<DateTime?>("MarkedAsAvailable");
b.Property<DateTime>("MarkedAsDenied");
b.Property<int>("ParentRequestId"); b.Property<int>("ParentRequestId");
b.Property<int>("RequestType"); b.Property<int>("RequestType");
@ -595,6 +595,12 @@ namespace Ombi.Store.Migrations
b.Property<int?>("IssueId"); b.Property<int?>("IssueId");
b.Property<DateTime>("MarkedAsApproved");
b.Property<DateTime?>("MarkedAsAvailable");
b.Property<DateTime>("MarkedAsDenied");
b.Property<string>("Overview"); b.Property<string>("Overview");
b.Property<string>("PosterPath"); b.Property<string>("PosterPath");

View file

@ -10,10 +10,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.9" /> <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.9" />
</ItemGroup> </ItemGroup>

View file

@ -7,12 +7,12 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.3" /> <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.1.1" />
<PackageReference Include="Moq" Version="4.7.99" /> <PackageReference Include="Moq" Version="4.7.99" />
<PackageReference Include="Nunit" Version="3.8.1" /> <PackageReference Include="Nunit" Version="3.8.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" /> <PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.7.0"></packagereference> <packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -11,7 +11,7 @@ namespace Ombi.Updater
ProcessInfo GetCurrentProcess(); ProcessInfo GetCurrentProcess();
int GetCurrentProcessId(); int GetCurrentProcessId();
ProcessInfo GetProcessById(int id); ProcessInfo GetProcessById(int id);
void Kill(StartupOptions opts); bool Kill(StartupOptions opts);
void KillAll(string processName); void KillAll(string processName);
void SetPriority(int processId, ProcessPriorityClass priority); void SetPriority(int processId, ProcessPriorityClass priority);
void WaitForExit(Process process); void WaitForExit(Process process);

View file

@ -22,29 +22,23 @@ namespace Ombi.Updater
{ {
// Kill Ombi Process // Kill Ombi Process
var p = new ProcessProvider(); var p = new ProcessProvider();
bool killed = false;
try try
{ {
p.Kill(opt); killed = p.Kill(opt);
} }
catch (Exception e) catch (Exception e)
{ {
Console.WriteLine(e); Console.WriteLine(e);
} }
// Make sure the process has been killed if (!killed)
while (p.FindProcessByName(opt.ProcessName).Any())
{ {
Thread.Sleep(500);
_log.LogDebug("Found another process called {0}, KILLING!", opt.ProcessName); _log.LogDebug("Couldn't kill the ombi process");
var proc = p.FindProcessByName(opt.ProcessName).FirstOrDefault(); return;
if (proc != null)
{
_log.LogDebug($"[{proc.Id}] - {proc.Name} - Path: {proc.StartPath}");
opt.OmbiProcessId = proc.Id;
p.Kill(opt);
}
} }
_log.LogDebug("Starting to move the files"); _log.LogDebug("Starting to move the files");
@ -111,21 +105,23 @@ namespace Ombi.Updater
var location = System.Reflection.Assembly.GetEntryAssembly().Location; var location = System.Reflection.Assembly.GetEntryAssembly().Location;
location = Path.GetDirectoryName(location); location = Path.GetDirectoryName(location);
_log.LogDebug("We are currently in dir {0}", location); _log.LogDebug("We are currently in dir {0}", location);
var updatedLocation = Directory.GetParent(location).FullName;
_log.LogDebug("The files are in {0}", updatedLocation); // Since the updater is a folder deeper
_log.LogDebug("Ombi is installed at {0}", options.ApplicationPath); _log.LogDebug("Ombi is installed at {0}", options.ApplicationPath);
//Now Create all of the directories //Now Create all of the directories
foreach (string dirPath in Directory.GetDirectories(location, "*", foreach (string dirPath in Directory.GetDirectories(updatedLocation, "*",
SearchOption.AllDirectories)) SearchOption.AllDirectories))
{ {
var newDir = dirPath.Replace(location, options.ApplicationPath); var newDir = dirPath.Replace(updatedLocation, options.ApplicationPath);
Directory.CreateDirectory(newDir); Directory.CreateDirectory(newDir);
_log.LogDebug("Created dir {0}", newDir); _log.LogDebug("Created dir {0}", newDir);
} }
//Copy all the files & Replaces any files with the same name //Copy all the files & Replaces any files with the same name
foreach (string currentPath in Directory.GetFiles(location, "*.*", foreach (string currentPath in Directory.GetFiles(updatedLocation, "*.*",
SearchOption.AllDirectories)) SearchOption.AllDirectories))
{ {
var newFile = currentPath.Replace(location, options.ApplicationPath); var newFile = currentPath.Replace(updatedLocation, options.ApplicationPath);
File.Copy(currentPath, newFile, true); File.Copy(currentPath, newFile, true);
_log.LogDebug("Replaced file {0}", newFile); _log.LogDebug("Replaced file {0}", newFile);
} }

View file

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RuntimeIdentifiers>win10-x64;win10-x86;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64;linux-arm;linux-arm64;</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64;win10-x86;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64;linux-arm;linux-arm64;</RuntimeIdentifiers>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion> <AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion> <FileVersion>3.0.0.0</FileVersion>
<Version></Version> <Version></Version>
@ -12,14 +12,14 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.1.1-beta" /> <PackageReference Include="CommandLineParser" Version="2.1.1-beta" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.2" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.2" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.0.2" /> <PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" /> <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.1.1" />
<PackageReference Include="Serilog" Version="2.6.0-dev-00892" /> <PackageReference Include="Serilog" Version="2.6.0-dev-00892" />
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" /> <PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Serilog.Sinks.File" Version="3.2.0" /> <PackageReference Include="Serilog.Sinks.File" Version="3.2.0" />

View file

@ -73,33 +73,32 @@ namespace Ombi.Updater
process.PriorityClass = priority; process.PriorityClass = priority;
} }
public void Kill(StartupOptions opts) public bool Kill(StartupOptions opts)
{
if (opts.IsWindowsService)
{
Console.WriteLine("Stopping Service {0}", opts.WindowsServiceName);
var process = new Process();
var startInfo =
new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = "cmd.exe",
Arguments = $"/C net stop \"{opts.WindowsServiceName}\""
};
process.StartInfo = startInfo;
process.Start();
}
else
{ {
//if (opts.IsWindowsService)
//{
// Console.WriteLine("Stopping Service {0}", opts.WindowsServiceName);
// var process = new Process();
// var startInfo =
// new ProcessStartInfo
// {
// WindowStyle = ProcessWindowStyle.Hidden,
// FileName = "cmd.exe",
// Arguments = $"/C net stop \"{opts.WindowsServiceName}\""
// };
// process.StartInfo = startInfo;
// process.Start();
//}
//else
//{
var process = Process.GetProcesses().FirstOrDefault(p => p.ProcessName == opts.ProcessName); var process = Process.GetProcesses().FirstOrDefault(p => p.ProcessName == opts.ProcessName);
if (process == null) if (process == null)
{ {
Console.WriteLine("Cannot find process with name: {0}", opts.ProcessName); Console.WriteLine("Cannot find process with name: {0}", opts.ProcessName);
return; return false;
} }
process.Refresh();
if (process.Id > 0) if (process.Id > 0)
{ {
@ -108,8 +107,12 @@ namespace Ombi.Updater
Console.WriteLine("[{0}]: Waiting for exit", process.Id); Console.WriteLine("[{0}]: Waiting for exit", process.Id);
process.WaitForExit(); process.WaitForExit();
Console.WriteLine("[{0}]: Process terminated successfully", process.Id); Console.WriteLine("[{0}]: Process terminated successfully", process.Id);
return true;
} }
}
return false;
//}
} }
public void KillAll(string processName) public void KillAll(string processName)

View file

@ -2,7 +2,7 @@
"profiles": { "profiles": {
"Ombi.Updater": { "Ombi.Updater": {
"commandName": "Project", "commandName": "Project",
"commandLineArgs": "--applicationPath \\\"C:\\\\Users\\\\Jamie\\\\Source\\\\Repos\\\\Ombi\\\\src\\\\Ombi\\\\bin\\\\Debug\\\\netcoreapp2.0\\\" --processname \\\"Ombi\\\" --startupArgs http://*:5000" "commandLineArgs": "--applicationPath \"C:\\_git\\ombi\\src\\Ombi.Updater\\bin\\Debug\\netcoreapp2.0\" --processname \"Ombi\""
} }
} }
} }

23
src/Ombi/.gitignore vendored
View file

@ -1,23 +1,10 @@
/wwwroot/css/** node_modules
/wwwroot/fonts/** bin
/wwwroot/lib/** obj
/wwwroot/maps/** wwwroot/dist
/wwwroot/dist/** *.log
/wwwroot/*.js.map
/wwwroot/*.js
# dependencies
/node_modules
/bower_components
# misc
/.sass-cache /.sass-cache
/connect.lock /connect.lock
/coverage/* /coverage/*
/libpeerconnection.log
npm-debug.log
testem.log
#/typings
/systemjs.config.js*
/Logs/** /Logs/**
**.db **.db

View file

@ -4,7 +4,7 @@
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{ {
"taskName": "restore", "label": "restore",
"command": "npm", "command": "npm",
"type": "shell", "type": "shell",
"args": [ "args": [
@ -14,7 +14,16 @@
"problemMatcher": [] "problemMatcher": []
}, },
{ {
"taskName": "build", "label": "clean",
"command": "dotnet",
"type": "shell",
"args": [
"clean"
],
"problemMatcher": "$msCompile"
},
{
"label": "build",
"command": "dotnet", "command": "dotnet",
"type": "shell", "type": "shell",
"args": [ "args": [
@ -27,7 +36,7 @@
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
}, },
{ {
"taskName": "lint", "label": "lint",
"type": "shell", "type": "shell",
"command": "npm", "command": "npm",
"args": [ "args": [

View file

@ -93,10 +93,32 @@ namespace Ombi
await context.Response.WriteAsync("Invalid API Key"); await context.Response.WriteAsync("Invalid API Key");
} }
else else
{
// Check if we have a UserName header if so we can impersonate that user
if (context.Request.Headers.Keys.Contains("UserName", StringComparer.InvariantCultureIgnoreCase))
{
var username = context.Request.Headers["UserName"].FirstOrDefault();
var um = context.RequestServices.GetService<OmbiUserManager>();
var user = await um.Users.FirstOrDefaultAsync(x =>
x.UserName.Equals(username, StringComparison.InvariantCultureIgnoreCase));
if (user == null)
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
await context.Response.WriteAsync("Invalid User");
await next.Invoke(context);
}
var roles = await um.GetRolesAsync(user);
var identity = new GenericIdentity(user.UserName);
var principal = new GenericPrincipal(identity, roles.ToArray());
context.User = principal;
}
else
{ {
var identity = new GenericIdentity("API"); var identity = new GenericIdentity("API");
var principal = new GenericPrincipal(identity, new[] { "Admin", "ApiUser" }); var principal = new GenericPrincipal(identity, new[] { "Admin", "ApiUser" });
context.User = principal; context.User = principal;
}
await next.Invoke(context); await next.Invoke(context);
} }
} }

View file

@ -1,7 +1,7 @@
import { animate, style, transition, trigger } from "@angular/animations"; import { animate, style, transition, trigger } from "@angular/animations";
import { AnimationEntryMetadata } from "@angular/core"; import { AnimationTriggerMetadata } from "@angular/animations";
export const fadeInOutAnimation: AnimationEntryMetadata = trigger("fadeInOut", [ export const fadeInOutAnimation: AnimationTriggerMetadata = trigger("fadeInOut", [
transition(":enter", [ // :enter is alias to 'void => *' transition(":enter", [ // :enter is alias to 'void => *'
style({ opacity: 0 }), style({ opacity: 0 }),
animate(1000, style({ opacity: 1 })), animate(1000, style({ opacity: 1 })),

View file

@ -134,6 +134,12 @@
<li [ngClass]="{'active': 'no' === translate.currentLang}"> <li [ngClass]="{'active': 'no' === translate.currentLang}">
<a (click)="translate.use('no')" [translate]="'NavigationBar.Language.Norwegian'"></a> <a (click)="translate.use('no')" [translate]="'NavigationBar.Language.Norwegian'"></a>
</li> </li>
<li [ngClass]="{'active': 'pt' === translate.currentLang}">
<a (click)="translate.use('pt')" [translate]="'NavigationBar.Language.BrazillianPortuguese'"></a>
</li>
<li [ngClass]="{'active': 'pl' === translate.currentLang}">
<a (click)="translate.use('pl')" [translate]="'NavigationBar.Language.Polish'"></a>
</li>
</ul> </ul>
</li> </li>
</ul> </ul>

View file

@ -40,13 +40,13 @@ export class AppComponent implements OnInit {
__webpack_public_path__ = base + "/dist/"; __webpack_public_path__ = base + "/dist/";
} }
this.translate.addLangs(["en", "de", "fr","da","es","it","nl","sv","no"]); this.translate.addLangs(["en", "de", "fr", "da", "es", "it", "nl", "sv", "no", "pl", "pt"]);
// this language will be used as a fallback when a translation isn't found in the current language // this language will be used as a fallback when a translation isn't found in the current language
this.translate.setDefaultLang("en"); this.translate.setDefaultLang("en");
// See if we can match the supported langs with the current browser lang // See if we can match the supported langs with the current browser lang
const browserLang: string = translate.getBrowserLang(); const browserLang: string = translate.getBrowserLang();
this.translate.use(browserLang.match(/en|fr|da|de|es|it|nl|sv|no/) ? browserLang : "en"); this.translate.use(browserLang.match(/en|fr|da|de|es|it|nl|sv|no|pl|pt/) ? browserLang : "en");
} }
public ngOnInit() { public ngOnInit() {

View file

@ -36,7 +36,7 @@ import { ImageService } from "./services";
import { LandingPageService } from "./services"; import { LandingPageService } from "./services";
import { NotificationService } from "./services"; import { NotificationService } from "./services";
import { SettingsService } from "./services"; import { SettingsService } from "./services";
import { IssuesService, JobService, StatusService } from "./services"; import { IssuesService, JobService, PlexTvService, StatusService } from "./services";
const routes: Routes = [ const routes: Routes = [
{ path: "*", component: PageNotFoundComponent }, { path: "*", component: PageNotFoundComponent },
@ -67,6 +67,14 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
return new TranslateHttpLoader(http, "/translations/", `.json?v=${version}`); return new TranslateHttpLoader(http, "/translations/", `.json?v=${version}`);
} }
export function JwtTokenGetter() {
const token = localStorage.getItem("id_token");
if (!token) {
return "";
}
return token;
}
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule.forRoot(routes), RouterModule.forRoot(routes),
@ -92,13 +100,7 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
CommonModule, CommonModule,
JwtModule.forRoot({ JwtModule.forRoot({
config: { config: {
tokenGetter: () => { tokenGetter: JwtTokenGetter,
const token = localStorage.getItem("id_token");
if (!token) {
return "";
}
return token;
},
}, },
}), }),
TranslateModule.forRoot({ TranslateModule.forRoot({
@ -133,6 +135,7 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
CookieService, CookieService,
JobService, JobService,
IssuesService, IssuesService,
PlexTvService,
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })

View file

@ -1,8 +1,11 @@
export interface IUserLogin { import { IPlexPin } from "../interfaces";
export interface IUserLogin {
username: string; username: string;
password: string; password: string;
rememberMe: boolean; rememberMe: boolean;
usePlexOAuth: boolean; usePlexOAuth: boolean;
plexTvPin: IPlexPin;
} }
export interface ILocalUser { export interface ILocalUser {

View file

@ -1,8 +1,8 @@
import { PlatformLocation } from "@angular/common"; import { PlatformLocation } from "@angular/common";
import { HttpClient } from "@angular/common/http"; import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { JwtHelperService } from "@auth0/angular-jwt"; import { JwtHelperService } from "@auth0/angular-jwt";
import { Observable } from "rxjs/Rx"; import { Observable } from "rxjs";
import { ServiceHelpers } from "../services"; import { ServiceHelpers } from "../services";
import { ILocalUser, IUserLogin } from "./IUserLogin"; import { ILocalUser, IUserLogin } from "./IUserLogin";

View file

@ -46,6 +46,7 @@ export interface IIssueComments {
} }
export interface IIssuesChat { export interface IIssuesChat {
id: number;
comment: string; comment: string;
date: Date; date: Date;
username: string; username: string;

View file

@ -2,6 +2,16 @@
user: IPlexUser; user: IPlexUser;
} }
export interface IPlexPin {
id: number;
code: string;
}
export interface IPlexOAuthViewModel {
wizard: boolean;
pin: IPlexPin;
}
export interface IPlexOAuthAccessToken { export interface IPlexOAuthAccessToken {
accessToken: string; accessToken: string;
} }
@ -24,6 +34,19 @@ export interface IPlexLibResponse {
data: IPlexLibraries; data: IPlexLibraries;
} }
export interface IPlexLibSimpleResponse {
successful: boolean;
message: string;
data: IPlexSection[];
}
export interface IPlexSection {
id: string;
key: string;
type: string;
title: string;
}
export interface IMediaContainer { export interface IMediaContainer {
directory: IDirectory[]; directory: IDirectory[];
} }
@ -39,6 +62,28 @@ export interface IPlexServerViewModel {
servers: IPlexServerResult; servers: IPlexServerResult;
} }
export interface IPlexServerAddViewModel {
success: boolean;
servers: IPlexServersAdd[];
}
export interface IPlexServersAdd {
serverId: number;
machineId: string;
serverName: string;
}
export interface IPlexUserViewModel {
username: string;
machineIdentifier: string;
libsSelected: number[];
}
export interface IPlexUserAddResponse {
success: boolean;
error: string;
}
export interface IPlexServerResult { export interface IPlexServerResult {
friendlyName: string; friendlyName: string;
machineIdentifier: string; machineIdentifier: string;

View file

@ -71,6 +71,10 @@ export interface ITvRequests {
status: string; status: string;
childRequests: IChildRequests[]; childRequests: IChildRequests[];
qualityOverride: number; qualityOverride: number;
background: any;
totalSeasons: number;
tvDbId: number;
open: boolean; // THIS IS FOR THE UI
// For UI display // For UI display
qualityOverrideTitle: string; qualityOverrideTitle: string;

View file

@ -28,8 +28,16 @@ export interface ISearchTvResult {
available: boolean; available: boolean;
plexUrl: string; plexUrl: string;
embyUrl: string; embyUrl: string;
quality: string;
firstSeason: boolean; firstSeason: boolean;
latestSeason: boolean; latestSeason: boolean;
theTvDbId: string;
subscribed: boolean;
showSubscribe: boolean;
fullyAvailable: boolean;
partlyAvailable: boolean;
background: any;
open: boolean; // THIS IS FOR THE UI
} }
export interface ITvRequestViewModel { export interface ITvRequestViewModel {

View file

@ -27,6 +27,7 @@ export interface IUpdateSettings extends ISettings {
windowsService: boolean; windowsService: boolean;
windowsServiceName: string; windowsServiceName: string;
isWindows: boolean; isWindows: boolean;
testMode: boolean;
} }
export interface IEmbySettings extends ISettings { export interface IEmbySettings extends ISettings {

View file

@ -23,6 +23,11 @@ export interface ICreateWizardUser {
usePlexAdminAccount: boolean; usePlexAdminAccount: boolean;
} }
export interface IWizardUserResult {
result: boolean;
errors: string[];
}
export enum UserType { export enum UserType {
LocalUser = 1, LocalUser = 1,
PlexUser = 2, PlexUser = 2,

View file

@ -51,6 +51,7 @@
<div *ngIf="comments" class="panel-body msg_container_base"> <div *ngIf="comments" class="panel-body msg_container_base">
<div *ngIf="comments.length <= 0" class="row msg_container base_receive"> <div *ngIf="comments.length <= 0" class="row msg_container base_receive">
<div class="col-md-10 col-xs-10"> <div class="col-md-10 col-xs-10">
<div class="messages msg_sent"> <div class="messages msg_sent">
<p [translate]="'Issues.NoComments'"></p> <p [translate]="'Issues.NoComments'"></p>
</div> </div>
@ -59,7 +60,8 @@
<div *ngFor="let comment of comments" class="row msg_container" [ngClass]="{'base_sent': comment.adminComment, 'base_receive': !comment.adminComment}"> <div *ngFor="let comment of comments" class="row msg_container" [ngClass]="{'base_sent': comment.adminComment, 'base_receive': !comment.adminComment}">
<div class="col-md-10 col-xs-10"> <div class="col-md-10 col-xs-10">
<div class="messages msg_sent">
<div class="messages msg_sent"> <i *ngIf="isAdmin" style="float:right;" class="fa fa-times" aria-hidden="true" (click)="deleteComment(comment.id)"></i>
<p>{{comment.comment}}</p> <p>{{comment.comment}}</p>
<time>{{comment.username}} • {{comment.date | date:'short'}}</time> <time>{{comment.username}} • {{comment.date | date:'short'}}</time>
</div> </div>

View file

@ -97,6 +97,13 @@ export class IssueDetailsComponent implements OnInit {
}); });
} }
public deleteComment(id: number) {
this.issueService.deleteComment(id).subscribe(x => {
this.loadComments();
this.notificationService.success("Comment Deleted");
});
}
private loadComments() { private loadComments() {
this.issueService.getComments(this.issueId).subscribe(x => this.comments = x); this.issueService.getComments(this.issueId).subscribe(x => this.comments = x);
} }

View file

@ -61,5 +61,4 @@ export class IssuesComponent implements OnInit {
this.resolvedIssues = x; this.resolvedIssues = x;
}); });
} }
} }

View file

@ -6,7 +6,7 @@ import { TranslateService } from "@ngx-translate/core";
import { PlatformLocation } from "@angular/common"; import { PlatformLocation } from "@angular/common";
import { AuthService } from "../auth/auth.service"; import { AuthService } from "../auth/auth.service";
import { IAuthenticationSettings, ICustomizationSettings } from "../interfaces"; import { IAuthenticationSettings, ICustomizationSettings } from "../interfaces";
import { NotificationService } from "../services"; import { NotificationService, PlexTvService } from "../services";
import { SettingsService } from "../services"; import { SettingsService } from "../services";
import { StatusService } from "../services"; import { StatusService } from "../services";
@ -30,6 +30,7 @@ export class LoginComponent implements OnDestroy, OnInit {
public landingFlag: boolean; public landingFlag: boolean;
public baseUrl: string; public baseUrl: string;
public loginWithOmbi: boolean; public loginWithOmbi: boolean;
public pinTimer: any;
public get appName(): string { public get appName(): string {
if (this.customizationSettings.applicationName) { if (this.customizationSettings.applicationName) {
@ -40,13 +41,14 @@ export class LoginComponent implements OnDestroy, OnInit {
} }
private timer: any; private timer: any;
private clientId: string;
private errorBody: string; private errorBody: string;
private errorValidation: string; private errorValidation: string;
constructor(private authService: AuthService, private router: Router, private notify: NotificationService, private status: StatusService, constructor(private authService: AuthService, private router: Router, private notify: NotificationService, private status: StatusService,
private fb: FormBuilder, private settingsService: SettingsService, private images: ImageService, private sanitizer: DomSanitizer, private fb: FormBuilder, private settingsService: SettingsService, private images: ImageService, private sanitizer: DomSanitizer,
private route: ActivatedRoute, private location: PlatformLocation, private readonly translate: TranslateService) { private route: ActivatedRoute, private location: PlatformLocation, private translate: TranslateService, private plexTv: PlexTvService) {
this.route.params this.route.params
.subscribe((params: any) => { .subscribe((params: any) => {
this.landingFlag = params.landing; this.landingFlag = params.landing;
@ -78,13 +80,14 @@ export class LoginComponent implements OnDestroy, OnInit {
public ngOnInit() { public ngOnInit() {
this.settingsService.getAuthentication().subscribe(x => this.authenticationSettings = x); this.settingsService.getAuthentication().subscribe(x => this.authenticationSettings = x);
this.settingsService.getClientId().subscribe(x => this.clientId = x);
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x);
this.images.getRandomBackground().subscribe(x => { this.images.getRandomBackground().subscribe(x => {
this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%),url(" + x.url + ")"); this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%),url(" + x.url + ")");
}); });
this.timer = setInterval(() => { this.timer = setInterval(() => {
this.cycleBackground(); this.cycleBackground();
}, 10000); }, 7000);
const base = this.location.getBaseHrefFromDOM(); const base = this.location.getBaseHrefFromDOM();
if (base.length > 1) { if (base.length > 1) {
@ -101,7 +104,7 @@ export class LoginComponent implements OnDestroy, OnInit {
return; return;
} }
const value = form.value; const value = form.value;
const user = { password: value.password, username: value.username, rememberMe: value.rememberMe, usePlexOAuth: false }; const user = { password: value.password, username: value.username, rememberMe: value.rememberMe, usePlexOAuth: false, plexTvPin: { id: 0, code: "" } };
this.authService.requiresPassword(user).subscribe(x => { this.authService.requiresPassword(user).subscribe(x => {
if (x && this.authenticationSettings.allowNoPassword) { if (x && this.authenticationSettings.allowNoPassword) {
// Looks like this user requires a password // Looks like this user requires a password
@ -113,6 +116,7 @@ export class LoginComponent implements OnDestroy, OnInit {
localStorage.setItem("id_token", x.access_token); localStorage.setItem("id_token", x.access_token);
if (this.authService.loggedIn()) { if (this.authService.loggedIn()) {
this.ngOnDestroy();
this.router.navigate(["search"]); this.router.navigate(["search"]);
} else { } else {
this.notify.error(this.errorBody); this.notify.error(this.errorBody);
@ -123,19 +127,49 @@ export class LoginComponent implements OnDestroy, OnInit {
} }
public oauth() { public oauth() {
this.authService.login({usePlexOAuth: true, password:"",rememberMe:true,username:""}).subscribe(x => { this.plexTv.GetPin(this.clientId, this.appName).subscribe((pin: any) => {
if (window.frameElement) {
// in frame this.authService.login({ usePlexOAuth: true, password: "", rememberMe: true, username: "", plexTvPin: pin }).subscribe(x => {
window.open(x.url, "_blank");
} else { window.open(x.url, "_blank", `toolbar=0,
// not in frame location=0,
window.location.href = x.url; status=0,
menubar=0,
scrollbars=1,
resizable=1,
width=500,
height=500`);
this.pinTimer = setInterval(() => {
this.notify.info("Authenticating", "Loading... Please Wait");
this.getPinResult(x.pinId);
}, 10000);
});
});
} }
public getPinResult(pinId: number) {
this.authService.oAuth(pinId).subscribe(x => {
if(x.access_token) {
localStorage.setItem("id_token", x.access_token);
if (this.authService.loggedIn()) {
this.ngOnDestroy();
this.router.navigate(["search"]);
return;
}
}
}, err => {
this.notify.error(err.statusText);
this.router.navigate(["login"]);
}); });
} }
public ngOnDestroy() { public ngOnDestroy() {
clearInterval(this.timer); clearInterval(this.timer);
clearInterval(this.pinTimer);
} }
private cycleBackground() { private cycleBackground() {

View file

@ -16,7 +16,6 @@ export class LoginOAuthComponent implements OnInit {
this.route.params this.route.params
.subscribe((params: any) => { .subscribe((params: any) => {
this.pin = params.pin; this.pin = params.pin;
}); });
} }
@ -40,7 +39,6 @@ export class LoginOAuthComponent implements OnInit {
}, err => { }, err => {
this.notify.error(err.statusText); this.notify.error(err.statusText);
this.router.navigate(["login"]); this.router.navigate(["login"]);
}); });
} }

View file

@ -1,5 +1,5 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { NguCarousel } from "@ngu/carousel"; import { NguCarouselConfig } from "@ngu/carousel";
import { ImageService, RecentlyAddedService } from "../services"; import { ImageService, RecentlyAddedService } from "../services";
import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "./../interfaces"; import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "./../interfaces";
@ -43,7 +43,7 @@ export class RecentlyAddedComponent implements OnInit {
public groupTv: boolean = false; public groupTv: boolean = false;
// https://github.com/sheikalthaf/ngu-carousel // https://github.com/sheikalthaf/ngu-carousel
public carouselTile: NguCarousel; public carouselTile: NguCarouselConfig;
constructor(private recentlyAddedService: RecentlyAddedService, constructor(private recentlyAddedService: RecentlyAddedService,
private imageService: ImageService) {} private imageService: ImageService) {}

View file

@ -45,8 +45,6 @@
<div> <div>
<div *ngFor="let request of movieRequests"> <div *ngFor="let request of movieRequests">
<div class="row"> <div class="row">
<div class="myBg backdrop" [style.background-image]="request.backgroundPath"></div> <div class="myBg backdrop" [style.background-image]="request.backgroundPath"></div>

View file

@ -1,15 +1,12 @@
import { PlatformLocation } from "@angular/common"; import { PlatformLocation } from "@angular/common";
import { Component, Input, OnInit } from "@angular/core"; import { Component, Input, OnInit } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser"; import { DomSanitizer } from "@angular/platform-browser";
import "rxjs/add/operator/debounceTime"; import { Subject } from "rxjs";
import "rxjs/add/operator/distinctUntilChanged"; import { debounceTime, distinctUntilChanged } from "rxjs/operators";
import "rxjs/add/operator/map";
import { Subject } from "rxjs/Subject";
import { AuthService } from "../auth/auth.service"; import { AuthService } from "../auth/auth.service";
import { NotificationService, RadarrService, RequestService } from "../services";
import { FilterType, IFilter, IIssueCategory, IMovieRequests, IPagenator, IRadarrProfile, IRadarrRootFolder, OrderType } from "../interfaces"; import { FilterType, IFilter, IIssueCategory, IMovieRequests, IPagenator, IRadarrProfile, IRadarrRootFolder, OrderType } from "../interfaces";
import { NotificationService, RadarrService, RequestService } from "../services";
@Component({ @Component({
selector: "movie-requests", selector: "movie-requests",
@ -45,16 +42,17 @@ export class MovieRequestsComponent implements OnInit {
private currentlyLoaded: number; private currentlyLoaded: number;
private amountToLoad: number; private amountToLoad: number;
constructor(private requestService: RequestService, constructor(
private requestService: RequestService,
private auth: AuthService, private auth: AuthService,
private notificationService: NotificationService, private notificationService: NotificationService,
private radarrService: RadarrService, private radarrService: RadarrService,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
private readonly platformLocation: PlatformLocation) { private readonly platformLocation: PlatformLocation) {
this.searchChanged this.searchChanged.pipe(
.debounceTime(600) // Wait Xms after the last event before emitting last event debounceTime(600), // Wait Xms after the last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value distinctUntilChanged(), // only emit if value is different from previous value
.subscribe(x => { ).subscribe(x => {
this.searchText = x as string; this.searchText = x as string;
if (this.searchText === "") { if (this.searchText === "") {
this.resetSearch(); this.resetSearch();

View file

@ -4,59 +4,41 @@
</div> </div>
</div> </div>
<br /> <br />
<!--TODO: I believe this +1 is causing off by one error skipping loading of tv shows
When removed and scrolling very slowly everything works as expected, however
if you scroll really quickly then you start getting duplicates of movies
since it's async and some subsequent results return first and then incrementer
is increased so you see movies which had already been gotten show up...
Removing infinte-scroll and setting max to 1000 till we work out some sort of fix
-->
<!--<div infinite-scroll
[infiniteScrollDistance]="1"
[infiniteScrollThrottle]="100"
(scrolled)="loadMore()">-->
<div> <div>
<p-treeTable [value]="tvRequests"> <div *ngFor="let node of tvRequests.collection">
<p-column>
<ng-template let-col let-node="rowData" pTemplate="header">
Results
</ng-template>
<ng-template let-col let-node="rowData" pTemplate="body">
<!--This is the section that holds the parent level results set--> <!--This is the section that holds the parent level results set-->
<div *ngIf="!node.leaf"> <div>
<div class="row"> <div class="row">
<div class="myBg backdrop" [style.background-image]="node?.data?.background"></div> <div class="myBg backdrop" [style.background-image]="node?.background"></div>
<div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div> <div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
<div class="col-sm-2 small-padding"> <div class="col-sm-2 small-padding">
<img class="img-responsive poster" src="{{node.data.posterPath || null}}" alt="poster"> <img class="img-responsive poster" src="{{node.posterPath || null}}" alt="poster">
</div> </div>
<div class="col-sm-5 small-padding"> <div class="col-sm-5 small-padding">
<div> <div>
<a href="http://www.imdb.com/title/{{node.data.imdbId}}/" target="_blank"> <a href="http://www.imdb.com/title/{{node.imdbId}}/" target="_blank">
<h4 class="request-title">{{node.data.title}} ({{node.data.releaseDate | date: 'yyyy'}})</h4> <h4 class="request-title">{{node.title}} ({{node.releaseDate | date: 'yyyy'}})</h4>
</a> </a>
</div> </div>
<br /> <br />
<div> <div>
<span>Status: </span> <span>Status: </span>
<span class="label label-success">{{node.data.status}}</span> <span class="label label-success">{{node.status}}</span>
</div> </div>
<div>Release Date: {{node.data.releaseDate | date}}</div> <div>Release Date: {{node.releaseDate | date}}</div>
<div *ngIf="isAdmin"> <div *ngIf="isAdmin">
<div *ngIf="node.data.qualityOverrideTitle" class="quality-override">{{ 'Requests.QualityOverride' | translate }} <div *ngIf="node.qualityOverrideTitle" class="quality-override">{{ 'Requests.QualityOverride' | translate }}
<span>{{node.data.qualityOverrideTitle}} </span> <span>{{node.qualityOverrideTitle}} </span>
</div> </div>
<div *ngIf="node.data.rootPathOverrideTitle" class="root-override">{{ 'Requests.RootFolderOverride' | translate }} <div *ngIf="node.rootPathOverrideTitle" class="root-override">{{ 'Requests.RootFolderOverride' | translate }}
<span>{{node.data.rootPathOverrideTitle}} </span> <span>{{node.rootPathOverrideTitle}} </span>
</div> </div>
</div> </div>
@ -64,7 +46,8 @@
</div> </div>
<div class="col-sm-3 col-sm-push-3 small-padding"> <div class="col-sm-3 col-sm-push-3 small-padding">
<button style="text-align: right" class="btn btn-sm btn-success-outline" (click)="openClosestTab($event)"><i class="fa fa-plus"></i> View</button> <button style="text-align: right" class="btn btn-sm btn-success-outline" (click)="openClosestTab(node,$event)">
<i class="fa fa-plus"></i> View</button>
<div *ngIf="isAdmin"> <div *ngIf="isAdmin">
<!--Sonarr Root Folder--> <!--Sonarr Root Folder-->
<div *ngIf="sonarrRootFolders" class="btn-group btn-split" id="rootFolderBtn"> <div *ngIf="sonarrRootFolders" class="btn-group btn-split" id="rootFolderBtn">
@ -77,7 +60,7 @@
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li *ngFor="let folder of sonarrRootFolders"> <li *ngFor="let folder of sonarrRootFolders">
<a href="#" (click)="selectRootFolder(node.data, folder, $event)">{{folder.path}}</a> <a href="#" (click)="selectRootFolder(node, folder, $event)">{{folder.path}}</a>
</li> </li>
</ul> </ul>
</div> </div>
@ -93,35 +76,39 @@
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li *ngFor="let profile of sonarrProfiles"> <li *ngFor="let profile of sonarrProfiles">
<a href="#" (click)="selectQualityProfile(node.data, profile, $event)">{{profile.name}}</a> <a href="#" (click)="selectQualityProfile(node, profile, $event)">{{profile.name}}</a>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
<div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issueBtn"> <div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issueBtn">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> <button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true">
<i class="fa fa-plus"></i> {{ 'Requests.ReportIssue' | translate }} <i class="fa fa-plus"></i> {{ 'Requests.ReportIssue' | translate }}
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li *ngFor="let cat of issueCategories"><a [routerLink]="" (click)="reportIssue(cat, node.data)">{{cat.value}}</a></li> <li *ngFor="let cat of issueCategories">
<a [routerLink]="" (click)="reportIssue(cat, node)">{{cat.value}}</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!--This is the section that holds the child seasons if they want to specify specific episodes--> <!--This is the section that holds the child seasons if they want to specify specific episodes-->
<div *ngIf="node.leaf"> <div *ngIf="node.open">
<tvrequests-children [childRequests]="node.data" [isAdmin] ="isAdmin" <tvrequests-children [childRequests]="node.childRequests" [isAdmin]="isAdmin" (requestDeleted)="childRequestDeleted($event)"></tvrequests-children>
(requestDeleted)="childRequestDeleted($event)"></tvrequests-children> </div>
<br/>
<br/>
</div> </div>
</ng-template>
</p-column>
</p-treeTable>
<p-paginator [rows]="10" [totalRecords]="totalTv" (onPageChange)="paginate($event)"></p-paginator> <p-paginator [rows]="10" [totalRecords]="totalTv" (onPageChange)="paginate($event)"></p-paginator>
</div> </div>
<issue-report [movie]="false" [visible]="issuesBarVisible" [title]="issueRequest?.title" <issue-report [movie]="false" [visible]="issuesBarVisible" [title]="issueRequest?.title" [issueCategory]="issueCategorySelected"
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]="issueProviderId" (visibleChange)="issuesBarVisible = $event;"></issue-report> [id]="issueRequest?.id" [providerId]="issueProviderId" (visibleChange)="issuesBarVisible = $event;"></issue-report>

View file

@ -1,21 +1,13 @@
import { PlatformLocation } from "@angular/common"; import { PlatformLocation } from "@angular/common";
import { Component, Input, OnInit } from "@angular/core"; import { Component, Input, OnInit } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser"; import { DomSanitizer } from "@angular/platform-browser";
import "rxjs/add/operator/debounceTime"; import { Subject } from "rxjs";
import "rxjs/add/operator/distinctUntilChanged"; import { debounceTime, distinctUntilChanged } from "rxjs/operators";
import "rxjs/add/operator/map";
import { Subject } from "rxjs/Subject";
import { ImageService } from "./../services/image.service";
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/distinctUntilChanged";
import "rxjs/add/operator/map";
import { AuthService } from "../auth/auth.service"; import { AuthService } from "../auth/auth.service";
import { FilterType, IIssueCategory, IPagenator, IRequestsViewModel, ISonarrProfile, ISonarrRootFolder, ITvRequests, OrderType } from "../interfaces";
import { NotificationService, RequestService, SonarrService } from "../services"; import { NotificationService, RequestService, SonarrService } from "../services";
import { ImageService } from "./../services/image.service";
import { TreeNode } from "primeng/primeng";
import { IIssueCategory, IPagenator, ISonarrProfile, ISonarrRootFolder, ITvRequests } from "../interfaces";
@Component({ @Component({
selector: "tv-requests", selector: "tv-requests",
@ -24,7 +16,7 @@ import { IIssueCategory, IPagenator, ISonarrProfile, ISonarrRootFolder, ITvRequ
}) })
export class TvRequestsComponent implements OnInit { export class TvRequestsComponent implements OnInit {
public tvRequests: TreeNode[]; public tvRequests: IRequestsViewModel<ITvRequests>;
public searchChanged = new Subject<string>(); public searchChanged = new Subject<string>();
public searchText: string; public searchText: string;
public isAdmin: boolean; public isAdmin: boolean;
@ -46,27 +38,49 @@ export class TvRequestsComponent implements OnInit {
private currentlyLoaded: number; private currentlyLoaded: number;
private amountToLoad: number; private amountToLoad: number;
constructor(private requestService: RequestService, constructor(
private requestService: RequestService,
private auth: AuthService, private auth: AuthService,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
private imageService: ImageService, private imageService: ImageService,
private sonarrService: SonarrService, private sonarrService: SonarrService,
private notificationService: NotificationService, private notificationService: NotificationService,
private readonly platformLocation: PlatformLocation) { private readonly platformLocation: PlatformLocation) {
this.searchChanged
.debounceTime(600) // Wait Xms after the last event before emitting last event this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
.distinctUntilChanged() // only emit if value is different from previous value if (this.isAdmin) {
.subscribe(x => { this.sonarrService.getQualityProfilesWithoutSettings()
.subscribe(x => this.sonarrProfiles = x);
this.sonarrService.getRootFoldersWithoutSettings()
.subscribe(x => this.sonarrRootFolders = x);
}
}
public openClosestTab(node: ITvRequests,el: any) {
el.preventDefault();
node.open = !node.open;
}
public ngOnInit() {
this.amountToLoad = 10;
this.currentlyLoaded = 10;
this.tvRequests = {collection:[], total:0};
this.searchChanged.pipe(
debounceTime(600), // Wait Xms after the last event before emitting last event
distinctUntilChanged(), // only emit if value is different from previous value
).subscribe(x => {
this.searchText = x as string; this.searchText = x as string;
if (this.searchText === "") { if (this.searchText === "") {
this.resetSearch(); this.resetSearch();
return; return;
} }
this.requestService.searchTvRequestsTree(this.searchText) this.requestService.searchTvRequests(this.searchText)
.subscribe(m => { .subscribe(m => {
this.tvRequests = m; this.tvRequests.collection = m;
this.tvRequests.forEach((val) => this.loadBackdrop(val)); this.tvRequests.collection.forEach((val) => this.loadBackdrop(val));
this.tvRequests.forEach((val) => this.setOverride(val.data)); this.tvRequests.collection.forEach((val) => this.setOverride(val));
}); });
}); });
this.defaultPoster = "../../../images/default_tv_poster.png"; this.defaultPoster = "../../../images/default_tv_poster.png";
@ -74,50 +88,6 @@ export class TvRequestsComponent implements OnInit {
if (base) { if (base) {
this.defaultPoster = "../../.." + base + "/images/default_tv_poster.png"; this.defaultPoster = "../../.." + base + "/images/default_tv_poster.png";
} }
}
public openClosestTab(el: any) {
const rowclass = "undefined ng-star-inserted";
el = el.toElement || el.relatedTarget || el.target || el.srcElement;
if (el.nodeName === "BUTTON") {
const isButtonAlreadyActive = el.parentElement.querySelector(".active");
// if a Button already has Class: .active
if (isButtonAlreadyActive) {
isButtonAlreadyActive.classList.remove("active");
} else {
el.className += " active";
}
}
while (el.className !== rowclass) {
// Increment the loop to the parent node until we find the row we need
el = el.parentNode;
}
// At this point, the while loop has stopped and `el` represents the element that has
// the class you specified
// Then we loop through the children to find the caret which we want to click
const caretright = "fa-caret-right";
const caretdown = "fa-caret-down";
for (const value of el.children) {
// the caret from the ui has 2 class selectors depending on if expanded or not
// we search for both since we want to still toggle the clicking
if (value.className.includes(caretright) || value.className.includes(caretdown)) {
// Then we tell JS to click the element even though we hid it from the UI
value.click();
//Break from loop since we no longer need to continue looking
break;
}
}
}
public ngOnInit() {
this.amountToLoad = 10;
this.currentlyLoaded = 10;
this.tvRequests = [];
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
this.loadInit(); this.loadInit();
} }
@ -125,7 +95,7 @@ export class TvRequestsComponent implements OnInit {
public paginate(event: IPagenator) { public paginate(event: IPagenator) {
const skipAmount = event.first; const skipAmount = event.first;
this.requestService.getTvRequestsTree(this.amountToLoad, skipAmount) this.requestService.getTvRequests(this.amountToLoad, skipAmount, OrderType.RequestedDateDesc, FilterType.None, FilterType.None)
.subscribe(x => { .subscribe(x => {
this.tvRequests = x; this.tvRequests = x;
this.currentlyLoaded = this.currentlyLoaded + this.amountToLoad; this.currentlyLoaded = this.currentlyLoaded + this.amountToLoad;
@ -150,14 +120,14 @@ export class TvRequestsComponent implements OnInit {
event.preventDefault(); event.preventDefault();
searchResult.rootFolder = rootFolderSelected.id; searchResult.rootFolder = rootFolderSelected.id;
this.setOverride(searchResult); this.setOverride(searchResult);
this.updateRequest(searchResult); this.setRootFolder(searchResult);
} }
public selectQualityProfile(searchResult: ITvRequests, profileSelected: ISonarrProfile, event: any) { public selectQualityProfile(searchResult: ITvRequests, profileSelected: ISonarrProfile, event: any) {
event.preventDefault(); event.preventDefault();
searchResult.qualityOverride = profileSelected.id; searchResult.qualityOverride = profileSelected.id;
this.setOverride(searchResult); this.setOverride(searchResult);
this.updateRequest(searchResult); this.setQualityProfile(searchResult);
} }
public reportIssue(catId: IIssueCategory, req: ITvRequests) { public reportIssue(catId: IIssueCategory, req: ITvRequests) {
@ -172,12 +142,23 @@ export class TvRequestsComponent implements OnInit {
this.setRootFolderOverrides(req); this.setRootFolderOverrides(req);
} }
private updateRequest(request: ITvRequests) { private setQualityProfile(req: ITvRequests) {
this.requestService.updateTvRequest(request) this.requestService.setQualityProfile(req.id, req.qualityOverride).subscribe(x => {
.subscribe(x => { if(x) {
this.notificationService.success("Request Updated"); this.notificationService.success("Quality profile updated");
this.setOverride(x); } else {
request = x; this.notificationService.error("Could not update the quality profile");
}
});
}
private setRootFolder(req: ITvRequests) {
this.requestService.setRootFolder(req.id, req.rootFolder).subscribe(x => {
if(x) {
this.notificationService.success("Quality profile updated");
} else {
this.notificationService.error("Could not update the quality profile");
}
}); });
} }
@ -204,23 +185,15 @@ export class TvRequestsComponent implements OnInit {
private loadInit() { private loadInit() {
this.requestService.getTotalTv().subscribe(x => this.totalTv = x); this.requestService.getTotalTv().subscribe(x => this.totalTv = x);
this.requestService.getTvRequestsTree(this.amountToLoad, 0) this.requestService.getTvRequests(this.amountToLoad, 0, OrderType.RequestedDateDesc, FilterType.None, FilterType.None)
.subscribe(x => { .subscribe(x => {
this.tvRequests = x; this.tvRequests = x;
this.tvRequests.forEach((val, index) => { this.tvRequests.collection.forEach((val, index) => {
this.setDefaults(val); this.setDefaults(val);
this.loadBackdrop(val); this.loadBackdrop(val);
this.setOverride(val.data); this.setOverride(val);
}); });
}); });
if(this.isAdmin) {
this.sonarrService.getQualityProfilesWithoutSettings()
.subscribe(x => this.sonarrProfiles = x);
this.sonarrService.getRootFoldersWithoutSettings()
.subscribe(x => this.sonarrRootFolders = x);
}
} }
private resetSearch() { private resetSearch() {
@ -228,20 +201,20 @@ export class TvRequestsComponent implements OnInit {
this.loadInit(); this.loadInit();
} }
private setDefaults(val: any) { private setDefaults(val: ITvRequests) {
if (val.data.posterPath === null) { if (val.posterPath === null) {
val.data.posterPath = this.defaultPoster; val.posterPath = this.defaultPoster;
} }
} }
private loadBackdrop(val: TreeNode): void { private loadBackdrop(val: ITvRequests): void {
if (val.data.background != null) { if (val.background != null) {
val.data.background = this.sanitizer.bypassSecurityTrustStyle val.background = this.sanitizer.bypassSecurityTrustStyle
("url(https://image.tmdb.org/t/p/w1280" + val.data.background + ")"); ("url(https://image.tmdb.org/t/p/w1280" + val.background + ")");
} else { } else {
this.imageService.getTvBanner(val.data.tvDbId).subscribe(x => { this.imageService.getTvBanner(val.tvDbId).subscribe(x => {
if (x) { if (x) {
val.data.background = this.sanitizer.bypassSecurityTrustStyle val.background = this.sanitizer.bypassSecurityTrustStyle
("url(" + x + ")"); ("url(" + x + ")");
} }
}); });

Some files were not shown because too many files have changed in this diff Show more