├── .editorconfig ├── .gitignore ├── Installation ├── PotPlayerSetup64.exe ├── Setup.bat ├── dotnet-sdk-3.1.426-win-x64.part1.rar ├── dotnet-sdk-3.1.426-win-x64.part2.rar └── dotnet-sdk-3.1.426-win-x64.part3.rar ├── LICENSE ├── MovieManager.BusinessLogic ├── ActorService.cs ├── AppStaticMethods.cs ├── AppStaticProperties.cs ├── DirectorService.cs ├── FileScanner.cs ├── GenreService.cs ├── MovieManager.BusinessLogic.csproj ├── MovieService.cs ├── PotPlayerService.cs ├── ScrapeService.cs ├── TagService.cs ├── UserSettingsService.cs └── XmlProcessor.cs ├── MovieManager.ClassLibrary ├── Actor │ ├── Actor.cs │ ├── ActorRangeRequest.cs │ ├── ActorTagMapping.cs │ └── ActorViewModel.cs ├── Genre │ ├── Genre.cs │ └── GenreViewModel.cs ├── Movie │ ├── Movie.cs │ ├── MovieActors.cs │ ├── MovieDetails.cs │ ├── MovieGenres.cs │ ├── MovieTags.cs │ └── MovieViewModel.cs ├── MovieManager.ClassLibrary.csproj ├── PlayList │ ├── PlayList.cs │ ├── PlayListItem.cs │ └── PlayListViewModel.cs ├── RequestBody │ ├── FilterRequest.cs │ ├── ImageRequest.cs │ └── SearchRequest.cs ├── Settings │ └── UserSettings.cs └── Tag │ ├── Tag.cs │ └── TagViewModel.cs ├── MovieManager.DB └── MovieDb_Clean.db ├── MovieManager.Data ├── DatabaseContext.cs ├── MovieManager.Data.csproj └── SqliteConfiguration.cs ├── MovieManager.Deployment ├── MovieManager.Deployment.csproj └── Program.cs ├── MovieManager.Endpoint ├── Controllers │ ├── ActorController.cs │ ├── DirectorController.cs │ ├── GenreController.cs │ ├── ImageController.cs │ ├── MovieController.cs │ ├── PlayListController.cs │ ├── TagController.cs │ └── UserSettingsController.cs ├── MovieManager.Endpoint.csproj ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── appsettings.Development.json └── appsettings.json ├── MovieManager.Testing ├── ActorServiceTest.cs ├── DatabaseContextTest.cs ├── FileScannerTest.cs ├── MovieManager.Testing.csproj ├── MovieServiceTest.cs ├── PotPlayerServiceTest.cs ├── ScrapeServiceTest.cs └── XmlEngineTest.cs ├── MovieManager.TrayApp ├── App.config ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── DelegateCommand.cs ├── InitializingWindow.xaml ├── InitializingWindow.xaml.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Movie.ico ├── MovieManager.TrayApp.csproj ├── NotifyIconResources.xaml └── NotifyIconViewModel.cs ├── MovieManager.Web ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── Constant.js │ ├── Imgs │ └── NotFound.jpg │ ├── components │ ├── ActorViewer.css │ ├── ActorViewer.jsx │ ├── DirectorViewer.css │ ├── DirectorViewer.jsx │ ├── GenreViewer.css │ ├── GenreViewer.jsx │ ├── MovieViewer.css │ ├── MovieViewer.jsx │ ├── SettingsViewer.css │ ├── SettingsViewer.jsx │ ├── TagViewer.css │ └── TagViewer.jsx │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── reportWebVitals.js │ ├── services │ └── DataService.js │ └── setupTests.js ├── MovieManager.sln ├── Readme.md ├── Readme ├── 1.jpg ├── 2.png ├── 3.png ├── 4.png ├── Picture10.png ├── Picture11.png ├── Picture12.png ├── Picture5.png ├── Picture6.png ├── Picture7.png ├── Picture8.png └── Picture9.png └── TestingMovieLib ├── AIKA ├── DASS-377-fanart.jpg ├── DASS-377-poster.jpg ├── DASS-377-thumb.jpg ├── DASS-377.mp4 └── DASS-377.nfo ├── 愛世くらら ├── IPX-100-fanart.jpg ├── IPX-100-poster.jpg ├── IPX-100-trailer.mp4 ├── IPX-100.mp4 └── IPX-100.nfo └── 滝冬ひかり,鈴音まゆ └── MIDV-679 ├── MIDV-679 彼女の双子のお姉さんが肉食すぎるアプローチで僕を誘惑 滝冬ひかり .mp4 ├── MIDV-679 彼女の双子のお姉さんが肉食すぎるアプローチで僕を誘惑 滝冬ひかり.nfo ├── extrafanart ├── extrafanart-1.jpg ├── extrafanart-10.jpg ├── extrafanart-11.jpg ├── extrafanart-12.jpg ├── extrafanart-13.jpg ├── extrafanart-2.jpg ├── extrafanart-3.jpg ├── extrafanart-4.jpg ├── extrafanart-5.jpg ├── extrafanart-6.jpg ├── extrafanart-7.jpg ├── extrafanart-8.jpg └── extrafanart-9.jpg ├── fanart.jpg ├── poster.jpg └── thumb.jpg /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. 4 | dotnet_diagnostic.CS8618.severity = none 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | 400 | MovieManager.DB/MovieDb_Me.db 401 | MovieManager.DB/MovieDb_Me.db-journal 402 | MovieManager.DB/MovieDb.db 403 | -------------------------------------------------------------------------------- /Installation/PotPlayerSetup64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Installation/PotPlayerSetup64.exe -------------------------------------------------------------------------------- /Installation/Setup.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | :: Check if running as administrator 5 | openfiles >nul 2>nul 6 | if %errorlevel% neq 0 ( 7 | echo Requesting administrative privileges... 8 | powershell -Command "Start-Process '%0' -Verb runAs" 9 | exit /b 10 | ) 11 | 12 | echo Step 1: Installing .NET Core 3.1 SDK... 13 | start /wait "" "%~dp0dotnet-sdk-3.1.426-win-x64.exe" /install /quiet /norestart 14 | if %errorlevel% neq 0 ( 15 | echo .NET Core 3.1 SDK installation failed. 16 | pause 17 | exit /b %errorlevel% 18 | ) 19 | echo .NET Core 3.1 SDK installed successfully. 20 | 21 | echo Step 2: Installing Node.js... 22 | start /wait msiexec /i "%~dp0node-v20.15.0-x64.msi" /quiet /norestart 23 | if %errorlevel% neq 0 ( 24 | echo Node.js installation failed. 25 | pause 26 | exit /b %errorlevel% 27 | ) 28 | echo Node.js installed successfully. 29 | 30 | echo Step 3: Installing PotPlayer... 31 | start /wait "%~dp0PotPlayerSetup64.exe" /s 32 | if %errorlevel% neq 0 ( 33 | echo PotPlayer installation failed. 34 | pause 35 | exit /b %errorlevel% 36 | ) 37 | echo PotPlayer installed successfully. 38 | 39 | echo Step 4: Installing serve and http-server globally... 40 | npm install --global serve 41 | if %errorlevel% neq 0 ( 42 | echo npm install serve failed. 43 | pause 44 | exit /b %errorlevel% 45 | ) 46 | npm install --global http-server 47 | if %errorlevel% neq 0 ( 48 | echo npm install http-server failed. 49 | pause 50 | exit /b %errorlevel% 51 | ) 52 | echo Node packages installed successfully. 53 | 54 | echo Installation Completed. 55 | pause 56 | endlocal 57 | -------------------------------------------------------------------------------- /Installation/dotnet-sdk-3.1.426-win-x64.part1.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Installation/dotnet-sdk-3.1.426-win-x64.part1.rar -------------------------------------------------------------------------------- /Installation/dotnet-sdk-3.1.426-win-x64.part2.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Installation/dotnet-sdk-3.1.426-win-x64.part2.rar -------------------------------------------------------------------------------- /Installation/dotnet-sdk-3.1.426-win-x64.part3.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Installation/dotnet-sdk-3.1.426-win-x64.part3.rar -------------------------------------------------------------------------------- /MovieManager.BusinessLogic/AppStaticMethods.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using System; 3 | using System.Diagnostics; 4 | using System.Management; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Threading; 8 | 9 | namespace MovieManager.BusinessLogic 10 | { 11 | public static class AppStaticMethods 12 | { 13 | public static string GetDiskPort(string disk) 14 | { 15 | if (String.IsNullOrEmpty(disk)) 16 | { 17 | return ""; 18 | } 19 | return $"http://127.0.0.1:{AppStaticProperties.diskPortMappings[disk]}//"; 20 | } 21 | 22 | public static void CreateHttpServer(int currentPort, string disk) 23 | { 24 | var info = new ProcessStartInfo("cmd.exe", "/K " + $"http-server {disk}:/ -p {currentPort}"); 25 | info.CreateNoWindow = true; 26 | AppStaticProperties.portHttpServerProcessMappings.Add(currentPort, Process.Start(info)); 27 | AppStaticProperties.diskPortMappings.Add(disk, currentPort); 28 | Log.Information($"Created http-server for {disk} drive at port {currentPort}"); 29 | Thread.Sleep(100); 30 | } 31 | 32 | public static void DisposeHttpServer(string disk) 33 | { 34 | var portNumber = AppStaticProperties.diskPortMappings[disk]; 35 | AppStaticProperties.diskPortMappings.Remove(disk); 36 | KillProcessAndChildrens(AppStaticProperties.portHttpServerProcessMappings[portNumber].Id); 37 | AppStaticProperties.portHttpServerProcessMappings.Remove(portNumber); 38 | } 39 | 40 | public static void KillProcessAndChildrens(int pid) 41 | { 42 | ManagementObjectSearcher processSearcher = new ManagementObjectSearcher 43 | ("Select * From Win32_Process Where ParentProcessID=" + pid); 44 | ManagementObjectCollection processCollection = processSearcher.Get(); 45 | 46 | try 47 | { 48 | Process proc = Process.GetProcessById(pid); 49 | if (!proc.HasExited) proc.Kill(); 50 | } 51 | catch (ArgumentException ex) 52 | { 53 | // Process already exited. 54 | Log.Warning($"Process already exited. {ex.ToString()}"); 55 | } 56 | 57 | if (processCollection != null) 58 | { 59 | foreach (ManagementObject mo in processCollection) 60 | { 61 | KillProcessAndChildrens(Convert.ToInt32(mo["ProcessID"])); 62 | } 63 | } 64 | } 65 | 66 | public static bool IsPortAvailable(int port) 67 | { 68 | try 69 | { 70 | TcpListener listener = new TcpListener(IPAddress.Any, port); 71 | listener.Start(); 72 | listener.Stop(); 73 | return true; 74 | } 75 | catch (SocketException) 76 | { 77 | return false; 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /MovieManager.BusinessLogic/AppStaticProperties.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Text; 5 | 6 | namespace MovieManager.BusinessLogic 7 | { 8 | public static class AppStaticProperties 9 | { 10 | public static string WebAppHost { get; set; } 11 | public static Dictionary diskPortMappings { get; set; } = new Dictionary(); 12 | public static Dictionary portHttpServerProcessMappings { get; set; } = new Dictionary(); 13 | } 14 | } -------------------------------------------------------------------------------- /MovieManager.BusinessLogic/DirectorService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace MovieManager.BusinessLogic 6 | { 7 | public class DirectorService 8 | { 9 | private MovieService _movieService; 10 | 11 | public DirectorService(MovieService movieService) 12 | { 13 | _movieService = movieService; 14 | } 15 | 16 | public List GetUniqueDirectors() 17 | { 18 | return _movieService.GetMovies().Select(x => x.Director).Distinct().ToList(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /MovieManager.BusinessLogic/FileScanner.cs: -------------------------------------------------------------------------------- 1 | using MovieManager.ClassLibrary; 2 | using Serilog; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MovieManager.BusinessLogic 11 | { 12 | public class FileScanner 13 | { 14 | private XmlProcessor _xmlEngine; 15 | private readonly List MovieExtensions = new List { ".avi", ".mp4", ".wmv", ".mkv" }; 16 | 17 | public FileScanner(XmlProcessor xmlEngine) 18 | { 19 | _xmlEngine = xmlEngine; 20 | } 21 | 22 | public List ScanFiles(string rootDirectory, int dateRange = -1) 23 | { 24 | var movies = new List(); 25 | try 26 | { 27 | var nfos = new List(); 28 | var allMovies = Directory.GetFiles(rootDirectory, $"*.*", SearchOption.AllDirectories) 29 | .Where(f => MovieExtensions.Any(f.ToLower().EndsWith)).ToList(); 30 | if (dateRange == -1) 31 | { 32 | nfos = Directory.GetFiles(rootDirectory, "*.nfo", SearchOption.AllDirectories).ToList(); 33 | } 34 | else 35 | { 36 | nfos = Directory.GetFiles(rootDirectory, "*.nfo", SearchOption.AllDirectories) 37 | .Where(f => File.GetCreationTime(f) >= DateTime.Now.AddDays(-dateRange)).ToList(); 38 | } 39 | movies = ProcessNfos(nfos, allMovies); 40 | } 41 | catch (Exception ex) 42 | { 43 | Log.Error($"An error occurs when scanning files! \n\r"); 44 | Log.Error(ex.ToString()); 45 | } 46 | return movies; 47 | } 48 | 49 | public List ScanFiles(string rootDirectory, DateTime startDate) 50 | { 51 | var movies = new List(); 52 | try 53 | { 54 | var nfos = new List(); 55 | var allMovies = Directory.GetFiles(rootDirectory, $"*.*", SearchOption.AllDirectories) 56 | .Where(f => MovieExtensions.Any(f.ToLower().EndsWith)).ToList(); 57 | nfos = Directory.GetFiles(rootDirectory, "*.nfo", SearchOption.AllDirectories) 58 | .Where(f => File.GetCreationTime(f) >= startDate).ToList(); 59 | movies = ProcessNfos(nfos, allMovies); 60 | } 61 | catch (Exception ex) 62 | { 63 | Log.Error($"An error occurs when scanning files! \n\r"); 64 | Log.Error(ex.ToString()); 65 | } 66 | return movies; 67 | } 68 | 69 | public List ScanFilesForImdbId(string rootDirectory) 70 | { 71 | var imdbIds = new List(); 72 | try 73 | { 74 | var nfos = new List(); 75 | var dirs = rootDirectory.Split("|"); 76 | foreach (var dir in dirs) 77 | { 78 | nfos = Directory.GetFiles(dir.Trim(), "*.nfo", SearchOption.AllDirectories).ToList(); 79 | foreach(var nfo in nfos) 80 | { 81 | try 82 | { 83 | var imdbId = _xmlEngine.ParseXmlFile(nfo)?.Title?.Split(' ')?[0]; 84 | if(!string.IsNullOrEmpty(imdbId)) 85 | { 86 | imdbIds.Add(imdbId); 87 | } 88 | } 89 | catch (Exception ex) 90 | { 91 | Log.Error($"An error occurs when scanning files for imdb ids! \n\r"); 92 | Log.Error(ex.ToString()); 93 | } 94 | 95 | } 96 | } 97 | } 98 | catch (Exception ex) 99 | { 100 | Log.Error($"An error occurs when scanning files for imdb ids! \n\r"); 101 | Log.Error(ex.ToString()); 102 | } 103 | return imdbIds; 104 | } 105 | 106 | private List ProcessNfos(List nfos, List allMovies) 107 | { 108 | var movies = new List(); 109 | var currentNfo = String.Empty; 110 | try 111 | { 112 | foreach (var nfo in nfos) 113 | { 114 | currentNfo = nfo; 115 | var movie = _xmlEngine.ParseXmlFile(nfo); 116 | if (movie != null) 117 | { 118 | var imdb = movie.Title.Split(' ')?[0]; 119 | if (!string.IsNullOrEmpty(imdb)) 120 | { 121 | // Update movie locations 122 | movie.ImdbId = imdb; 123 | var movieFileLoc = allMovies.Where(x => x.Contains(imdb)).ToList(); 124 | if (movieFileLoc?.Count() > 0) 125 | { 126 | var sb = new StringBuilder(); 127 | foreach (var loc in movieFileLoc) 128 | { 129 | sb.Append(loc + "|"); 130 | movie.MovieLocation = sb.ToString(); 131 | } 132 | // Update fanart/poster 133 | var nfoFileName = Path.GetFileNameWithoutExtension(nfo); 134 | var moviePath = Path.GetDirectoryName(movieFileLoc.FirstOrDefault()); 135 | var movieName = Path.GetFileNameWithoutExtension(movieFileLoc.FirstOrDefault()); 136 | var fanArtLoc = Directory.GetFiles(moviePath, $"{movieName}-fanart.jpg").FirstOrDefault(); 137 | if (string.IsNullOrEmpty(fanArtLoc)) 138 | { 139 | fanArtLoc = Directory.GetFiles(moviePath, $"{nfoFileName}-fanart.jpg").FirstOrDefault(); 140 | if (string.IsNullOrEmpty(fanArtLoc)) 141 | { 142 | fanArtLoc = Directory.GetFiles(moviePath) 143 | .Where(file => Path.GetFileName(file).Contains("fanart", StringComparison.OrdinalIgnoreCase)) 144 | .FirstOrDefault(); 145 | } 146 | } 147 | var posterLoc = Directory.GetFiles(moviePath, $"{movieName}-poster.jpg").FirstOrDefault(); 148 | if (string.IsNullOrEmpty(posterLoc)) 149 | { 150 | posterLoc = Directory.GetFiles(moviePath, $"{nfoFileName}-poster.jpg").FirstOrDefault(); 151 | if (string.IsNullOrEmpty(posterLoc)) 152 | { 153 | posterLoc = Directory.GetFiles(moviePath) 154 | .Where(file => Path.GetFileName(file).Contains("poster", StringComparison.OrdinalIgnoreCase)) 155 | .FirstOrDefault(); 156 | } 157 | } 158 | movie.FanArtLocation = fanArtLoc; 159 | movie.PosterFileLocation = posterLoc; 160 | // Update created date 161 | movie.DateAdded = File.GetCreationTime(movieFileLoc.FirstOrDefault()).ToString("yyyy-MM-dd"); 162 | 163 | movies.Add(movie); 164 | } 165 | else 166 | { 167 | Log.Warning($"Skipped nfo files: {currentNfo} because the movie location is null.\n\r"); 168 | } 169 | } 170 | } 171 | } 172 | } 173 | catch(Exception ex) 174 | { 175 | Log.Error($"An error occurs when processing nfo files: {currentNfo} \n\r"); 176 | Log.Error(ex.ToString()); 177 | } 178 | return movies; 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /MovieManager.BusinessLogic/GenreService.cs: -------------------------------------------------------------------------------- 1 | using MovieManager.ClassLibrary; 2 | using MovieManager.Data; 3 | using Serilog; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Globalization; 7 | using System.Linq; 8 | 9 | namespace MovieManager.BusinessLogic 10 | { 11 | public class GenreService 12 | { 13 | public GenreService() 14 | { 15 | CultureInfo PronoCi = new CultureInfo(2052); 16 | } 17 | 18 | public List GetAll () 19 | { 20 | var results = new List(); 21 | try 22 | { 23 | using (var dbContext = new DatabaseContext()) 24 | { 25 | results = dbContext.Genres.ToList(); 26 | results.Sort(); 27 | } 28 | } 29 | catch (Exception ex) 30 | { 31 | Log.Error(ex.ToString()); 32 | } 33 | return results; 34 | } 35 | 36 | public List Get(string searchString) 37 | { 38 | var results = new List(); 39 | try 40 | { 41 | using (var dbContext = new DatabaseContext()) 42 | { 43 | var sqlString = @$"select * from Genre where Name like '%{searchString}%'"; 44 | results = dbContext.Database.SqlQuery(sqlString).ToList(); 45 | results.Sort(); 46 | } 47 | } 48 | catch (Exception ex) 49 | { 50 | Log.Error(ex.ToString()); 51 | } 52 | return results; 53 | } 54 | 55 | public List GetAllNames() 56 | { 57 | var results = new List(); 58 | try 59 | { 60 | using (var dbContext = new DatabaseContext()) 61 | { 62 | results = dbContext.Genres.Select(x => x.Name).ToList(); 63 | results.Sort(); 64 | } 65 | } 66 | catch (Exception ex) 67 | { 68 | Log.Error($"An error occurs when getting genre names. \n\r"); 69 | Log.Error(ex.ToString()); 70 | } 71 | return results; 72 | } 73 | 74 | public List GetLikedGenres() 75 | { 76 | var results = new List(); 77 | try 78 | { 79 | using (var dbContext = new DatabaseContext()) 80 | { 81 | results = dbContext.Genres.Where(x => x.Liked).Select(x => x.Name).ToList(); 82 | } 83 | } 84 | catch (Exception ex) 85 | { 86 | Log.Error($"An error occurs when getting liked actor. \n\r"); 87 | Log.Error(ex.ToString()); 88 | } 89 | return results; 90 | } 91 | 92 | public bool LikeGenre(string genreName) 93 | { 94 | try 95 | { 96 | using (var context = new DatabaseContext()) 97 | { 98 | var genre = context.Genres.Where(x => x.Name == genreName).FirstOrDefault(); 99 | genre.Liked = !genre.Liked; 100 | context.SaveChanges(); 101 | return genre.Liked; 102 | } 103 | 104 | } 105 | catch (Exception ex) 106 | { 107 | Log.Error($"An error occurs when setting genre's like flag. \n\r"); 108 | Log.Error(ex.ToString()); 109 | } 110 | return false; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /MovieManager.BusinessLogic/MovieManager.BusinessLogic.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /MovieManager.BusinessLogic/PotPlayerService.cs: -------------------------------------------------------------------------------- 1 | using MovieManager.ClassLibrary; 2 | using MovieManager.Data; 3 | using Serilog; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading; 10 | 11 | namespace MovieManager.BusinessLogic 12 | { 13 | public class PotPlayerService 14 | { 15 | private MovieService _movieService; 16 | 17 | public PotPlayerService(MovieService movieService) 18 | { 19 | _movieService = movieService; 20 | } 21 | 22 | public void BuildPlayList(string playListName, string path, List movies, FileMode fileMode = FileMode.Create) 23 | { 24 | if (playListName.Contains("undefined")) 25 | { 26 | playListName = playListName.Replace("undefined", $"{DateTime.Now.ToString("yyyyMMdd_hhmmss")}"); 27 | } 28 | try 29 | { 30 | var movieLocations = movies.Select(x => x.MovieLocation.Split("|").Where(x => !string.IsNullOrEmpty(x)).ToList()).ToList(); 31 | var imdbIds = movies.Select(x => x.ImdbId).ToList(); 32 | var fs = new FileStream($"{path}\\{playListName.Replace(":", "-")}.dpl", fileMode); 33 | using(var writer = new StreamWriter(fs)) 34 | { 35 | if(fileMode == FileMode.Create) 36 | { 37 | var defaultInput = "DAUMPLAYLIST\nplaytime=0\ntopindex=0\nfoldertype=2\nsaveplaypos=0\n"; 38 | writer.WriteLine(defaultInput); 39 | } 40 | var count = 1; 41 | for (int i = 0; i < movieLocations.Count; i++) 42 | { 43 | foreach (var movieLocation in movieLocations[i]) 44 | { 45 | var l = $"{count++}*file*{movieLocation}"; 46 | writer.WriteLine(l); 47 | } 48 | } 49 | } 50 | using(var context = new DatabaseContext()) 51 | { 52 | foreach(var imdbId in imdbIds) 53 | { 54 | var movie = context.Movies.Where(x => x.ImdbId == imdbId).FirstOrDefault(); 55 | if(movie != null) 56 | { 57 | movie.PlayedCount += 1; 58 | } 59 | context.SaveChanges(); 60 | } 61 | } 62 | } 63 | catch(Exception ex) 64 | { 65 | Log.Error($"An error occurs when creating potplayer list. \n\r"); 66 | Log.Error(ex.ToString()); 67 | } 68 | } 69 | 70 | public void BuildPlayListByActors(string playListName, string path, List actors, FileMode fileMode = FileMode.Create) 71 | { 72 | try 73 | { 74 | var imdbIds = new HashSet(); 75 | var movieLocations = new List(); 76 | foreach(var actor in actors) 77 | { 78 | var movies = _movieService.GetMoviesByFilters(FilterType.Actors, new List { actor }, false).ToList(); 79 | foreach (var m in movies) 80 | { 81 | var imdbId = m.ImdbId; 82 | var currentMovies = m.MovieLocation.Split("|").Where(x => !string.IsNullOrEmpty(x)).ToList(); 83 | if (!string.IsNullOrEmpty(imdbId) && !imdbIds.Contains(imdbId) && currentMovies.Count > 0) 84 | { 85 | imdbIds.Add(imdbId); 86 | movieLocations.AddRange(currentMovies); 87 | } 88 | } 89 | } 90 | 91 | var fs = new FileStream($"{path}\\{playListName.Replace(":", "-")}.dpl", fileMode); 92 | using (var writer = new StreamWriter(fs)) 93 | { 94 | if (fileMode == FileMode.Create) 95 | { 96 | var defaultInput = "DAUMPLAYLIST\nplaytime=0\ntopindex=0\nfoldertype=2\nsaveplaypos=0\n"; 97 | writer.WriteLine(defaultInput); 98 | } 99 | for (int i = 0; i < movieLocations.Count; i++) 100 | { 101 | writer.WriteLine($"{i + 1}*file*{movieLocations[i]}"); 102 | } 103 | } 104 | using (var context = new DatabaseContext()) 105 | { 106 | foreach (var imdbId in imdbIds) 107 | { 108 | var movie = context.Movies.Where(x => x.ImdbId == imdbId).FirstOrDefault(); 109 | if (movie != null) 110 | { 111 | movie.PlayedCount += 1; 112 | } 113 | context.SaveChanges(); 114 | } 115 | } 116 | } 117 | catch (Exception ex) 118 | { 119 | Log.Error($"An error occurs when creating potplayer list. \n\r"); 120 | Log.Error(ex.ToString()); 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /MovieManager.BusinessLogic/ScrapeService.cs: -------------------------------------------------------------------------------- 1 | using MovieManager.ClassLibrary; 2 | using MovieManager.Data; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using HtmlAgilityPack; 8 | using Serilog; 9 | using System.Threading.Tasks; 10 | using System.Threading; 11 | using System.IO; 12 | using System.Windows.Controls; 13 | using System.Xml.Linq; 14 | 15 | namespace MovieManager.BusinessLogic 16 | { 17 | public class ScrapeService 18 | { 19 | public void GetActorInformation() 20 | { 21 | try 22 | { 23 | using (var context = new DatabaseContext()) 24 | { 25 | var sqlString = $"select * from Actor where LastUpdated IS NULL"; 26 | var actorNames = context.Database.SqlQuery(sqlString).Select(x => x.Name).ToList(); 27 | //var actorNames = new List() { "徳永しおり" }; 28 | var index = 0; 29 | 30 | foreach (var name in actorNames) 31 | { 32 | var actor = context.Actors.Where(x => x.Name == name).FirstOrDefault(); 33 | Log.Debug($"Thread {Thread.CurrentThread.ManagedThreadId}: Start to process {index} : {actor.Name}"); 34 | var cleanActorName = string.Empty; 35 | if (actor.Name.Contains('(')) 36 | { 37 | cleanActorName = actor.Name.Substring(0, actor.Name.IndexOf('(')); 38 | } 39 | else 40 | { 41 | cleanActorName = actor.Name; 42 | } 43 | 44 | try 45 | { 46 | var queryPage = $@"https://www.minnano-av.com/search_result.php?search_scope=actress&search_word={cleanActorName}&search=+Go+"; 47 | HtmlWeb web = new HtmlWeb(); 48 | var htmlDoc = web.Load(queryPage); 49 | var actorScorePage = htmlDoc.DocumentNode.SelectSingleNode("//div[@class='act-area']"); 50 | var actorInfoPage = htmlDoc.DocumentNode.SelectSingleNode("//div[@class='act-profile']"); 51 | 52 | // Actor Info 53 | if (actorInfoPage != null && !string.IsNullOrEmpty(actorInfoPage?.InnerText)) 54 | { 55 | var dobInfo = actorInfoPage.SelectSingleNode("//span[text()='生年月日']/following-sibling::p"); 56 | var bodyInfo = actorInfoPage.SelectSingleNode("//span[text()='サイズ']/following-sibling::p"); 57 | if (dobInfo == null) 58 | { 59 | Log.Error($"Can't find {actor.Name} date of birth information!"); 60 | } 61 | else 62 | { 63 | DateTime temp = new DateTime(); 64 | if (DateTime.TryParse(dobInfo.InnerText.Trim().Substring(0, 11), out temp)) 65 | { 66 | actor.DateofBirth = temp.ToString("yyyy-MM-dd"); 67 | } 68 | } 69 | if (bodyInfo == null) 70 | { 71 | Log.Error($"Can't find {actor.Name} body information!"); 72 | } 73 | else 74 | { 75 | var bodyInfoList = bodyInfo.InnerText.Trim().Split("/"); 76 | actor.Height = String.IsNullOrEmpty(bodyInfoList[0].Replace("T", "").Trim().ToString()) ? "" : $"{bodyInfoList[0].Replace("T", "").Trim()}cm"; 77 | actor.Cup = bodyInfoList[1].Trim().IndexOf("(") == -1 ? "" : $"{bodyInfoList[1].Trim().Substring(bodyInfoList[1].Trim().IndexOf("(") + 1, 1)} Cup"; 78 | actor.Bust = bodyInfoList[1].Trim().IndexOf("(") == -1 ? "" : $"{bodyInfoList[1].Replace("B", "").Trim().Substring(0, bodyInfoList[1].Trim().IndexOf("(") - 1)}"; 79 | actor.Waist = $"{bodyInfoList[2].Replace("W", "").Trim()}"; 80 | actor.Hips = $"{bodyInfoList[3].Replace("H", "").Trim()}"; 81 | } 82 | } 83 | else 84 | { 85 | Log.Error($"Can't find {actor.Name} information!"); 86 | } 87 | // Actor scores 88 | if (actorScorePage != null && !string.IsNullOrEmpty(actorInfoPage?.InnerText)) 89 | { 90 | var scoreNodes = actorScorePage.SelectNodes(".//td[@class='t9']"); 91 | if (scoreNodes != null && scoreNodes?.Count >= 9) 92 | { 93 | actor.Looks = scoreNodes[1]?.InnerText?.Trim(); 94 | actor.Body = scoreNodes[3]?.InnerText?.Trim(); 95 | actor.SexAppeal = scoreNodes[7]?.InnerText?.Trim(); 96 | actor.Overall = scoreNodes[9]?.InnerText?.Trim(); 97 | } 98 | else 99 | { 100 | Log.Error($"Can't find {actor.Name} score!"); 101 | } 102 | } 103 | else 104 | { 105 | Log.Error($"Can't find {actor.Name} score!"); 106 | } 107 | actor.LastUpdated = DateTime.Now.ToString("yyyy-MM-dd"); 108 | context.SaveChanges(); 109 | Thread.Sleep(1000); 110 | Log.Debug($"Thread {Thread.CurrentThread.ManagedThreadId}: complete to process {index} : {actor.Name}"); 111 | index++; 112 | } 113 | catch (Exception ex) 114 | { 115 | Log.Error($"An error occurs when processing actor{cleanActorName} information! \n\r"); 116 | Log.Error(ex.ToString()); 117 | } 118 | } 119 | } 120 | } 121 | catch (Exception ex) 122 | { 123 | Log.Error(ex.ToString()); 124 | } 125 | } 126 | 127 | private string GetActorInfoHtml_Deprecated(string actorName) 128 | { 129 | var actorInfoPage = string.Empty; 130 | 131 | var queryPage = $@"https://xslist.org/search?query={actorName}"; 132 | HtmlWeb web = new HtmlWeb(); 133 | var htmlDoc = web.Load(queryPage); 134 | var nodes = htmlDoc.DocumentNode.SelectNodes("//a"); 135 | if (nodes != null) 136 | { 137 | foreach (var node in nodes) 138 | { 139 | var title = node.GetAttributes("title").FirstOrDefault()?.Value; 140 | if (!string.IsNullOrEmpty(title)) 141 | { 142 | if (title.EndsWith(actorName)) 143 | { 144 | actorInfoPage = node.GetAttributes("href").FirstOrDefault()?.Value; 145 | break; 146 | } 147 | } 148 | } 149 | } 150 | return actorInfoPage; 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /MovieManager.BusinessLogic/TagService.cs: -------------------------------------------------------------------------------- 1 | using MovieManager.ClassLibrary; 2 | using MovieManager.Data; 3 | using Serilog; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Globalization; 7 | using System.Linq; 8 | 9 | namespace MovieManager.BusinessLogic 10 | { 11 | public class TagService 12 | { 13 | public TagService() 14 | { 15 | CultureInfo PronoCi = new CultureInfo(2052); 16 | } 17 | 18 | public List GetAll() 19 | { 20 | var results = new List(); 21 | try 22 | { 23 | using (var dbContext = new DatabaseContext()) 24 | { 25 | results = dbContext.Tags.ToList(); 26 | results.Sort(); 27 | } 28 | } 29 | catch (Exception ex) 30 | { 31 | Log.Error(ex.ToString()); 32 | } 33 | return results; 34 | } 35 | 36 | public List Get(string searchString) 37 | { 38 | var results = new List(); 39 | try 40 | { 41 | using (var dbContext = new DatabaseContext()) 42 | { 43 | var sqlString = @$"select * from Tag where Name like '%{searchString}%'"; 44 | results = dbContext.Database.SqlQuery(sqlString).ToList(); 45 | results.Sort(); 46 | } 47 | } 48 | catch (Exception ex) 49 | { 50 | Log.Error(ex.ToString()); 51 | } 52 | return results; 53 | } 54 | 55 | public List GetAllNames() 56 | { 57 | var results = new List(); 58 | try 59 | { 60 | using (var dbContext = new DatabaseContext()) 61 | { 62 | results = dbContext.Tags.Select(x => x.Name).ToList(); 63 | results.Sort(); 64 | } 65 | } 66 | catch (Exception ex) 67 | { 68 | Log.Error($"An error occurs when getting tag names. \n\r"); 69 | Log.Error(ex.ToString()); 70 | } 71 | return results; 72 | } 73 | 74 | public List GetLikedTags() 75 | { 76 | var results = new List(); 77 | try 78 | { 79 | using (var dbContext = new DatabaseContext()) 80 | { 81 | results = dbContext.Tags.Where(x => x.Liked).Select(x => x.Name).ToList(); 82 | } 83 | } 84 | catch (Exception ex) 85 | { 86 | Log.Error($"An error occurs when getting liked tags. \n\r"); 87 | Log.Error(ex.ToString()); 88 | } 89 | return results; 90 | } 91 | 92 | public bool LikeTag(string tagName) 93 | { 94 | try 95 | { 96 | using (var context = new DatabaseContext()) 97 | { 98 | var tag = context.Tags.Where(x => x.Name == tagName).FirstOrDefault(); 99 | tag.Liked = !tag.Liked; 100 | context.SaveChanges(); 101 | return tag.Liked; 102 | } 103 | 104 | } 105 | catch (Exception ex) 106 | { 107 | Log.Error($"An error occurs when setting tag's like flag. \n\r"); 108 | Log.Error(ex.ToString()); 109 | } 110 | return false; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /MovieManager.BusinessLogic/UserSettingsService.cs: -------------------------------------------------------------------------------- 1 | using MovieManager.ClassLibrary; 2 | using MovieManager.Data; 3 | using Serilog; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace MovieManager.BusinessLogic 9 | { 10 | public class UserSettingsService 11 | { 12 | private MovieService _movieService; 13 | private XmlProcessor _xmlProcessor; 14 | 15 | public UserSettingsService(MovieService movieService 16 | ,XmlProcessor xmlProcessor) 17 | { 18 | _movieService = movieService; 19 | _xmlProcessor = xmlProcessor; 20 | } 21 | 22 | 23 | public UserSettings GetUserSettings() 24 | { 25 | var settings = new UserSettings(); 26 | try 27 | { 28 | using (var dbContext = new DatabaseContext()) 29 | { 30 | var sqlString = "select Value from UserSettings where Name = 'MovieDirectory'"; 31 | settings.MovieDirectory = dbContext.Database.SqlQuery(sqlString).FirstOrDefault(); 32 | sqlString = "select Value from UserSettings where Name = 'ActorFiguresDMMDirectory'"; 33 | settings.ActorFiguresDMMDirectory = dbContext.Database.SqlQuery(sqlString).FirstOrDefault(); 34 | sqlString = "select Value from UserSettings where Name = 'ActorFiguresAllDirectory'"; 35 | settings.ActorFiguresAllDirectory = dbContext.Database.SqlQuery(sqlString).FirstOrDefault(); 36 | sqlString = "select Value from UserSettings where Name = 'PotPlayerDirectory'"; 37 | settings.PotPlayerDirectory = dbContext.Database.SqlQuery(sqlString).FirstOrDefault(); 38 | } 39 | } 40 | catch (Exception ex) 41 | { 42 | Log.Error(ex.ToString()); 43 | } 44 | return settings; 45 | } 46 | 47 | public void SetUserSettings(UserSettings settings) 48 | { 49 | try 50 | { 51 | var prevMovieDir = GetUserSettings().MovieDirectory; 52 | using (var dbContext = new DatabaseContext()) 53 | { 54 | dbContext.Database.ExecuteSqlCommand($"update UserSettings set value = '{settings.MovieDirectory}' where Name = 'MovieDirectory'"); 55 | dbContext.Database.ExecuteSqlCommand($"update UserSettings set value = '{settings.ActorFiguresDMMDirectory}' where Name = 'ActorFiguresDMMDirectory'"); 56 | dbContext.Database.ExecuteSqlCommand($"update UserSettings set value = '{settings.ActorFiguresAllDirectory}' where Name = 'ActorFiguresAllDirectory'"); 57 | dbContext.Database.ExecuteSqlCommand($"update UserSettings set value = '{settings.PotPlayerDirectory}' where Name = 'PotPlayerDirectory'"); 58 | } 59 | 60 | var newMovieDir = GetUserSettings().MovieDirectory; 61 | var movieDirToRemove = new List(); 62 | // Remove added movies from previous movie directories. 63 | if (!string.IsNullOrEmpty(prevMovieDir) && !string.IsNullOrEmpty(newMovieDir)) 64 | { 65 | var preMovieDirs = prevMovieDir.Split("|"); 66 | var newMovieDirs = newMovieDir.Split("|"); 67 | foreach (var preMovieDir in preMovieDirs) 68 | { 69 | if (!newMovieDir.Contains(preMovieDir)) 70 | { 71 | movieDirToRemove.Add(preMovieDir); 72 | } 73 | } 74 | } 75 | foreach (var dir in movieDirToRemove) 76 | { 77 | var scanner = new FileScanner(_xmlProcessor); 78 | _movieService.DeleteMoviesFromDirectory(scanner.ScanFilesForImdbId(dir)); 79 | } 80 | 81 | } 82 | catch (Exception ex) 83 | { 84 | Log.Error(ex.ToString()); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /MovieManager.BusinessLogic/XmlProcessor.cs: -------------------------------------------------------------------------------- 1 | using MovieManager.ClassLibrary; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Xml; 5 | using System.Linq; 6 | using Serilog; 7 | 8 | namespace MovieManager.BusinessLogic 9 | { 10 | public class XmlProcessor 11 | { 12 | public Movie ParseXmlFile(string xmlFileLocation) 13 | { 14 | Movie movie = null; 15 | try 16 | { 17 | var xmlDoc = new XmlDocument(); 18 | xmlDoc.Load(xmlFileLocation); 19 | var title = xmlDoc.GetElementsByTagName("title")[0]?.InnerText; 20 | var plot = xmlDoc.GetElementsByTagName("plot")[0]?.InnerText; 21 | var year = int.Parse(!String.IsNullOrEmpty(xmlDoc.GetElementsByTagName("year")[0]?.InnerText) ? 22 | xmlDoc.GetElementsByTagName("year")[0]?.InnerText : DateTime.Now.Year.ToString()); 23 | var runtime = int.Parse(!String.IsNullOrEmpty(xmlDoc.GetElementsByTagName("runtime")[0]?.InnerText) ? 24 | xmlDoc.GetElementsByTagName("runtime")[0]?.InnerText : "0"); 25 | var studio = xmlDoc.GetElementsByTagName("studio")[0]?.InnerText; 26 | var releaseDate = xmlDoc.GetElementsByTagName("release")[0]?.InnerText ?? xmlDoc.GetElementsByTagName("releasedate")[0]?.InnerText; 27 | var director = xmlDoc.GetElementsByTagName("director")[0]?.InnerText; 28 | var genres = GetGenres(title, xmlDoc.GetElementsByTagName("genre")); 29 | var tags = GetTags(title, xmlDoc.GetElementsByTagName("tag")); 30 | var actors = GetActors(xmlDoc.GetElementsByTagName("actor")); 31 | var label = xmlDoc.GetElementsByTagName("label")[0]?.InnerText; 32 | 33 | if (!string.IsNullOrEmpty(label)) 34 | { 35 | genres.Add(label); 36 | tags.Add(label); 37 | } 38 | 39 | movie = new Movie() 40 | { 41 | Title = title, 42 | Plot = plot, 43 | Year = year, 44 | Runtime = runtime, 45 | Director = director, 46 | Studio = studio, 47 | ReleaseDate = releaseDate, 48 | Genres = genres, 49 | Tags = tags, 50 | Actors = actors 51 | }; 52 | } 53 | catch(Exception ex) 54 | { 55 | Log.Error($"An error occurs when processing xml: {xmlFileLocation}. \n\r"); 56 | Log.Error(ex.ToString()); 57 | } 58 | return movie; 59 | } 60 | 61 | private List GetGenres(string title, XmlNodeList rawGenres) 62 | { 63 | var genres = new List(); 64 | foreach(var rawGenre in rawGenres) 65 | { 66 | var genre = ((XmlNode)rawGenre).InnerText.Trim(); 67 | if (!string.IsNullOrEmpty(genre) && !title.Contains(genre)) 68 | { 69 | genres.Add(((XmlNode)rawGenre).InnerText); 70 | } 71 | } 72 | return genres; 73 | } 74 | 75 | private List GetTags(string title, XmlNodeList rawTags) 76 | { 77 | var tags = new List(); 78 | foreach (var rawTag in rawTags) 79 | { 80 | var tag = ((XmlNode)rawTag).InnerText.Trim(); 81 | if(!string.IsNullOrEmpty(tag) && !title.Contains(tag)) 82 | { 83 | tags.Add(tag); 84 | } 85 | } 86 | return tags; 87 | } 88 | 89 | private List GetActors(XmlNodeList rawActors) 90 | { 91 | var actors = new List(); 92 | foreach (XmlNode rawActor in rawActors) 93 | { 94 | foreach(XmlNode n in rawActor.SelectNodes("name")) 95 | { 96 | var actor = n.InnerText.Trim(); 97 | if(!string.IsNullOrEmpty(actor)) 98 | { 99 | actors.Add(actor); 100 | } 101 | } 102 | } 103 | return actors; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/Actor/Actor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace MovieManager.ClassLibrary 6 | { 7 | public class Actor 8 | { 9 | [Key] 10 | public string Name { get; set; } 11 | public string DateofBirth { get; set; } 12 | public string Height { get; set; } 13 | public string Cup { get; set; } 14 | public string Bust { get; set; } 15 | public string Waist { get; set; } 16 | public string Hips { get; set; } 17 | public string Looks { get; set; } 18 | public string Body { get; set; } 19 | public string SexAppeal { get; set; } 20 | public string Overall { get; set; } 21 | [NotMapped] 22 | public List Tags { get; set; } 23 | public string LastUpdated { get; set; } 24 | public bool Liked { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/Actor/ActorRangeRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace MovieManager.ClassLibrary 6 | { 7 | public class ActorRangeRequest 8 | { 9 | public int HeightUpper { get; set; } 10 | public int HeightLower { get; set; } 11 | public string CupUpper { get; set; } 12 | public string CupLower { get; set; } 13 | public decimal BustLower { get; set; } 14 | public decimal BustUpper { get; set; } 15 | public decimal WaistLower { get; set; } 16 | public decimal WaistUpper { get; set; } 17 | public decimal HipsLower { get; set; } 18 | public decimal HipsUpper { get; set; } 19 | public decimal LooksLower { get; set; } 20 | public decimal LooksUpper { get; set; } 21 | public decimal BodyLower { get; set; } 22 | public decimal BodyUpper { get; set; } 23 | public decimal SexAppealLower { get; set; } 24 | public decimal SexAppealUpper { get; set; } 25 | public decimal OverallLower { get; set; } 26 | public decimal OverallUpper { get; set; } 27 | public int Age { get; set; } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/Actor/ActorTagMapping.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace MovieManager.ClassLibrary 4 | { 5 | public class ActorTagMapping 6 | { 7 | [Key] 8 | public int Id { get; set; } 9 | public string ActorName { get; set; } 10 | public string TagName { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/Actor/ActorViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MovieManager.ClassLibrary 5 | { 6 | public class ActorViewModel 7 | { 8 | public string Name { get; set; } 9 | public string DateofBirth { get; set; } 10 | public string Height { get; set; } 11 | public string Cup { get; set; } 12 | public string Bust { get; set; } 13 | public string Waist { get; set; } 14 | public string?Hips { get; set; } 15 | public string Looks { get; set; } 16 | public string Body { get; set; } 17 | public string SexAppeal { get; set; } 18 | public string Overall { get; set; } 19 | public List Tags { get; set; } 20 | public string LastUpdated { get; set; } 21 | public bool Liked { get; set; } 22 | public string FigureSmallPath { get; set; } 23 | public string FigureLargePath { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/Genre/Genre.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace MovieManager.ClassLibrary 4 | { 5 | public class Genre 6 | { 7 | [Key] 8 | public string Name { get; set; } 9 | public bool Liked { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/Genre/GenreViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace MovieManager.ClassLibrary 6 | { 7 | public class GenreViewModel 8 | { 9 | public string Name { get; set; } 10 | public bool Liked { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/Movie/Movie.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace MovieManager.ClassLibrary 6 | { 7 | public class Movie 8 | { 9 | [Key] 10 | public string ImdbId { get; set; } 11 | public string Title { get; set; } 12 | public string Plot { get; set; } 13 | public int Year { get; set; } 14 | public int Runtime { get; set; } 15 | public string Director { get; set; } 16 | public string Studio { get; set; } 17 | public string PosterFileLocation { get; set; } 18 | public string FanArtLocation { get; set; } 19 | public string MovieLocation { get; set; } 20 | public int PlayedCount { get; set; } 21 | public string DateAdded { get; set; } 22 | public string ReleaseDate { get; set; } 23 | public bool Liked { get; set; } 24 | [NotMapped] 25 | public List Genres { get; set; } 26 | [NotMapped] 27 | public List Tags { get; set; } 28 | [NotMapped] 29 | public List Actors { get; set; } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/Movie/MovieActors.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace MovieManager.ClassLibrary 4 | { 5 | public class MovieActors 6 | { 7 | [Key] 8 | public int Id { get; set; } 9 | public string ImdbId { get; set; } 10 | public string ActorName { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/Movie/MovieDetails.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace MovieManager.ClassLibrary 6 | { 7 | public class MovieDetails 8 | { 9 | public string ImdbId { get; set; } 10 | public string Title { get; set; } 11 | public string Plot { get; set; } 12 | public int Year { get; set; } 13 | public int Runtime { get; set; } 14 | public string Studio { get; set; } 15 | public string PosterFileLocation { get; set; } 16 | public string FanArtLocation { get; set; } 17 | public string MovieLocation { get; set; } 18 | public int PlayedCount { get; set; } 19 | public string DateAdded { get; set; } 20 | public string ReleaseDate { get; set; } 21 | public bool Liked { get; set; } 22 | public List Genres { get; set; } 23 | public List Tags { get; set; } 24 | public List Actors { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/Movie/MovieGenres.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace MovieManager.ClassLibrary 4 | { 5 | public class MovieGenres 6 | { 7 | [Key] 8 | public int Id { get; set; } 9 | public string ImdbId { get; set; } 10 | public string GenreName { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/Movie/MovieTags.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace MovieManager.ClassLibrary 4 | { 5 | public class MovieTags 6 | { 7 | [Key] 8 | public int Id { get; set; } 9 | public string ImdbId { get; set; } 10 | public string TagName { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/Movie/MovieViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | 4 | namespace MovieManager.ClassLibrary 5 | { 6 | public class MovieViewModel 7 | { 8 | public string ImdbId { get; set; } 9 | public string Title { get; set; } 10 | public string Director { get; set; } 11 | public string PosterFileLocation { get; set; } 12 | public string FanArtLocation { get; set; } 13 | public string MovieLocation { get; set; } 14 | public string DateAdded { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/MovieManager.ClassLibrary.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1-windows7.0 5 | enable 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/PlayList/PlayList.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace MovieManager.ClassLibrary 4 | { 5 | public class PlayList 6 | { 7 | [Key] 8 | public int Id { get; set; } 9 | public string Name { get; set; } 10 | public bool Liked { get; set; } 11 | public string ImdbId { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/PlayList/PlayListItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace MovieManager.ClassLibrary 6 | { 7 | public class PlayListItem 8 | { 9 | public string ImdbId { get; set; } 10 | public string MovieLocation { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/PlayList/PlayListViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace MovieManager.ClassLibrary 2 | { 3 | public class PlayListViewModel 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } 7 | public bool Liked { get; set; } 8 | public int ImdbId { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/RequestBody/FilterRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MovieManager.ClassLibrary 4 | { 5 | public enum FilterType { Actors, Genres, Tags, Directors, Years } 6 | 7 | public class FilterRequest 8 | { 9 | public FilterType FilterType { get; set; } 10 | public List Filters { get; set; } 11 | public bool IsAndOperator { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/RequestBody/ImageRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace MovieManager.ClassLibrary.RequestBody 6 | { 7 | public class ImageRequest 8 | { 9 | /// 10 | /// 0: poster, 1: fanart 11 | /// 10: actor thumbsnail, 11: actor large portrait 12 | /// 13 | public int ImageType { get; set; } 14 | public string Id { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/RequestBody/SearchRequest.cs: -------------------------------------------------------------------------------- 1 | namespace MovieManager.ClassLibrary 2 | { 3 | public class SearchRequest 4 | { 5 | public string SearchType { get; set; } 6 | public string SearchString { get; set; } 7 | public string SearchString2 { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/Settings/UserSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace MovieManager.ClassLibrary 7 | { 8 | public class UserSettings 9 | { 10 | public string MovieDirectory { get; set; } 11 | public string ActorFiguresDMMDirectory { get; set; } 12 | public string ActorFiguresAllDirectory { get; set; } 13 | public string PotPlayerDirectory { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/Tag/Tag.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace MovieManager.ClassLibrary 4 | { 5 | public class Tag 6 | { 7 | [Key] 8 | public string Name { get; set; } 9 | public bool Liked { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MovieManager.ClassLibrary/Tag/TagViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace MovieManager.ClassLibrary 6 | { 7 | public class TagViewModel 8 | { 9 | public string Name { get; set; } 10 | public bool Liked { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MovieManager.DB/MovieDb_Clean.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/MovieManager.DB/MovieDb_Clean.db -------------------------------------------------------------------------------- /MovieManager.Data/DatabaseContext.cs: -------------------------------------------------------------------------------- 1 | using MovieManager.ClassLibrary; 2 | using System.Configuration; 3 | using System.Data.Entity; 4 | using System.Data.Entity.ModelConfiguration.Conventions; 5 | using System.Data.SQLite; 6 | using System.IO; 7 | 8 | namespace MovieManager.Data 9 | { 10 | public class DatabaseContext: DbContext 11 | { 12 | 13 | public DatabaseContext(): base(new SQLiteConnection() 14 | { 15 | ConnectionString = new SQLiteConnectionStringBuilder() { DataSource = ConfigurationManager.AppSettings["DatabaseLocation"], ForeignKeys = true }.ConnectionString 16 | }, true) { } 17 | 18 | protected override void OnModelCreating(DbModelBuilder modelBuilder) 19 | { 20 | modelBuilder.Conventions.Remove(); 21 | base.OnModelCreating(modelBuilder); 22 | } 23 | 24 | public DbSet Actors { get; set; } 25 | public DbSet Genres { get; set; } 26 | public DbSet Movies { get; set; } 27 | public DbSet MovieActors { get; set; } 28 | public DbSet MovieGenres { get; set; } 29 | public DbSet MovieTags { get; set; } 30 | public DbSet PlayLists { get; set; } 31 | public DbSet Tags { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MovieManager.Data/MovieManager.Data.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1-windows7.0 5 | enable 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Never 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /MovieManager.Data/SqliteConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Entity; 2 | using System.Data.Entity.Core.Common; 3 | using System.Data.SQLite; 4 | using System.Data.SQLite.EF6; 5 | 6 | namespace MovieManager.Data 7 | { 8 | public class SqliteConfiguration: DbConfiguration 9 | { 10 | public SqliteConfiguration() 11 | { 12 | SetProviderFactory("System.Data.SQLite", SQLiteFactory.Instance); 13 | SetProviderFactory("System.Data.SQLite.EF6", SQLiteProviderFactory.Instance); 14 | SetProviderServices("System.Data.SQLite", (DbProviderServices)SQLiteProviderFactory.Instance.GetService(typeof(DbProviderServices))); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MovieManager.Deployment/MovieManager.Deployment.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /MovieManager.Deployment/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Newtonsoft.Json.Linq; 4 | using System.Configuration; 5 | using System.Diagnostics; 6 | using System.Xml; 7 | 8 | namespace MovieManager.Deployment 9 | { 10 | class Program 11 | { 12 | static void Main() 13 | { 14 | // Step 1: Build MovieManager.Tray 15 | // Assuming the build process is done manually or using a build tool 16 | // You can use a build tool like MSBuild or a script to automate the build process 17 | // Here's an example of how you can use MSBuild to build the MovieManager.Tray projec 18 | string solutionDirectory = Environment.CurrentDirectory; 19 | for (int i = 0; i < 4; i++) 20 | { 21 | solutionDirectory = Directory.GetParent(solutionDirectory).FullName; 22 | } 23 | string trayProjectPath = $@"{solutionDirectory}\MovieManager.TrayApp\MovieManager.TrayApp.csproj"; 24 | string msBuildPath = @"C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"; 25 | 26 | ProcessStartInfo startInfo = new ProcessStartInfo 27 | { 28 | FileName = msBuildPath, 29 | Arguments = $"{trayProjectPath} /p:Configuration=Release /p:Platform=\"Any CPU\"", 30 | UseShellExecute = false, 31 | CreateNoWindow = true 32 | }; 33 | 34 | Process buildProcess = Process.Start(startInfo); 35 | buildProcess.WaitForExit(); 36 | 37 | 38 | // Step 2: Copy build folder to Tray build folder 39 | string webBuildFolder = $@"{solutionDirectory}\MovieManager.Web\build"; 40 | string trayBuildFolder = $@"{solutionDirectory}\MovieManager.TrayApp\bin\Any CPU\Release\netcoreapp3.1"; 41 | CopyDirectory(webBuildFolder, $@"{trayBuildFolder}\build"); 42 | 43 | // Step 3: Copy test lib folder to Tray build folder 44 | string testLib = $@"{solutionDirectory}\TestingMovieLib"; 45 | CopyDirectory(testLib, $@"{trayBuildFolder}\TestingMovieLib"); 46 | 47 | // Step 4: Update appsettings.json in Tray build folder 48 | string appSettingsPath = Path.Combine(trayBuildFolder, "appsettings.json"); 49 | UpdateAppSettings(appSettingsPath, "WebAppDirectory", "build"); 50 | 51 | // Step 5: Update MovieManager.TrayApp.dll.config in Tray build folder 52 | string configFilePath = Path.Combine(trayBuildFolder, "MovieManager.TrayApp.dll.config"); 53 | UpdateConfig(configFilePath, "DatabaseLocation", "MovieDb.db"); 54 | 55 | // Step 6: Copy MovieDb_Clean.db to Tray build folder 56 | string dbSourcePath = $@"{solutionDirectory}\MovieManager.DB\MovieDb_Clean.db"; 57 | string dbDestPath = Path.Combine(trayBuildFolder, "MovieDb.db"); 58 | File.Copy(dbSourcePath, dbDestPath, true); 59 | 60 | // Step 7: Rename Tray build folder 61 | string newFolderName = $"MovieManager_{DateTime.Now.ToString("MMddyyyy_hhmmss")}"; 62 | string newFolderPath = Path.Combine(Path.GetDirectoryName(trayBuildFolder), newFolderName); 63 | Directory.Move(trayBuildFolder, newFolderPath); 64 | } 65 | 66 | static void CopyDirectory(string sourceDir, string targetDir) 67 | { 68 | DirectoryInfo dir = new DirectoryInfo(sourceDir); 69 | FileSystemInfo[] fileSystemInfos = dir.GetFileSystemInfos(); 70 | 71 | if (!Directory.Exists(targetDir)) 72 | { 73 | Directory.CreateDirectory(targetDir); 74 | } 75 | 76 | foreach (FileSystemInfo fileSystemInfo in fileSystemInfos) 77 | { 78 | string sourcePath = fileSystemInfo.FullName; 79 | string targetPath = Path.Combine(targetDir, fileSystemInfo.Name); 80 | 81 | if (fileSystemInfo.GetType() == typeof(DirectoryInfo)) 82 | { 83 | CopyDirectory(sourcePath, targetPath); 84 | } 85 | else 86 | { 87 | File.Copy(sourcePath, targetPath, true); 88 | } 89 | } 90 | } 91 | 92 | static void UpdateAppSettings(string filePath, string key, string value) 93 | { 94 | string json = File.ReadAllText(filePath); 95 | JObject jObject = JObject.Parse(json); 96 | jObject["AppSettings"][key] = value; 97 | File.WriteAllText(filePath, jObject.ToString()); 98 | } 99 | 100 | static void UpdateConfig(string filePath, string key, string value) 101 | { 102 | XmlDocument xmlDoc = new XmlDocument(); 103 | xmlDoc.Load(filePath); 104 | 105 | XmlNode node = xmlDoc.SelectSingleNode($"//appSettings/add[@key='{key}']"); 106 | if (node != null) 107 | { 108 | node.Attributes["value"].Value = value; 109 | } 110 | else 111 | { 112 | XmlElement element = xmlDoc.CreateElement("add"); 113 | XmlAttribute attributeKey = xmlDoc.CreateAttribute("key"); 114 | attributeKey.Value = key; 115 | element.Attributes.Append(attributeKey); 116 | 117 | XmlAttribute attributeValue = xmlDoc.CreateAttribute("value"); 118 | attributeValue.Value = value; 119 | element.Attributes.Append(attributeValue); 120 | 121 | XmlNode appSettingsNode = xmlDoc.SelectSingleNode("//appSettings"); 122 | appSettingsNode.AppendChild(element); 123 | } 124 | 125 | xmlDoc.Save(filePath); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /MovieManager.Endpoint/Controllers/ActorController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using MovieManager.BusinessLogic; 3 | using MovieManager.ClassLibrary; 4 | using System.Collections.Generic; 5 | 6 | namespace MovieManager.Endpoint.Controllers 7 | { 8 | [Route("api/[controller]")] 9 | [ApiController] 10 | public class ActorController : ControllerBase 11 | { 12 | private string notFoundMessage = "No actors found!"; 13 | private string badRequestMessage = "Value cannot be null!"; 14 | private ActorService _actorService; 15 | 16 | public ActorController(ActorService actorService) 17 | { 18 | _actorService = actorService; 19 | } 20 | 21 | [HttpGet] 22 | [Route("/actors/all")] 23 | public ActionResult GetAll() 24 | { 25 | var actors = _actorService.GetAll(); 26 | if (actors.Count > 0) 27 | { 28 | return Ok(actors); 29 | } 30 | return NotFound(notFoundMessage); 31 | } 32 | 33 | [HttpGet] 34 | [Route("/actors/names")] 35 | public ActionResult GetAllNames() 36 | { 37 | var actors = _actorService.GetAllNames(); 38 | if (actors.Count > 0) 39 | { 40 | return Ok(actors); 41 | } 42 | return NotFound(notFoundMessage); 43 | } 44 | 45 | [HttpGet] 46 | [Route("/actors/{searchString}")] 47 | public ActionResult GetByName(string searchString) 48 | { 49 | var actors = _actorService.GetByName(searchString); 50 | if (actors.Count > 0) 51 | { 52 | return Ok(actors); 53 | } 54 | return NotFound(notFoundMessage); 55 | } 56 | 57 | [HttpPost] 58 | [Route("/actors/getbynames")] 59 | public ActionResult GetByNames([FromBody] List names) 60 | { 61 | var actors = _actorService.GetByNames(names); 62 | if (actors.Count > 0) 63 | { 64 | return Ok(actors); 65 | } 66 | return NotFound(notFoundMessage); 67 | } 68 | 69 | [HttpPost] 70 | [Route("/actors/ranges")] 71 | public ActionResult GetNamesByRange([FromBody] ActorRangeRequest actorRangeRequest) 72 | { 73 | var actors = _actorService.GetNamesByRange( 74 | actorRangeRequest.HeightLower, 75 | actorRangeRequest.HeightUpper, 76 | actorRangeRequest.CupLower, 77 | actorRangeRequest.CupUpper, 78 | actorRangeRequest.Age); 79 | return Ok(actors); 80 | } 81 | 82 | [HttpGet] 83 | [Route("/actors/like")] 84 | public ActionResult GetLikedActorNames() 85 | { 86 | var actors = _actorService.GetLikedActorNames(); 87 | if (actors.Count > 0) 88 | { 89 | return Ok(actors); 90 | } 91 | return NotFound(notFoundMessage); 92 | } 93 | 94 | [HttpPut] 95 | [Route("/actors/like/{actorName}")] 96 | public ActionResult LikeActor(string actorName) 97 | { 98 | if (string.IsNullOrEmpty(actorName)) 99 | { 100 | return BadRequest(badRequestMessage); 101 | } 102 | return Ok(_actorService.LikeActor(actorName)); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /MovieManager.Endpoint/Controllers/DirectorController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using MovieManager.BusinessLogic; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace MovieManager.Endpoint.Controllers 9 | { 10 | [Route("api/[controller]")] 11 | [ApiController] 12 | public class DirectorController : Controller 13 | { 14 | private string notFoundMessage = "No directors found!"; 15 | private string badRequestMessage = "Value cannot be null!"; 16 | private DirectorService _directorService; 17 | 18 | public DirectorController(DirectorService directorService) 19 | { 20 | _directorService = directorService; 21 | } 22 | 23 | [HttpGet] 24 | [Route("/directors/names")] 25 | public ActionResult GetAllNames() 26 | { 27 | var directors = _directorService.GetUniqueDirectors(); 28 | if (directors.Count > 0) 29 | { 30 | return Ok(directors); 31 | } 32 | return NotFound(notFoundMessage); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MovieManager.Endpoint/Controllers/GenreController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using MovieManager.BusinessLogic; 3 | 4 | namespace MovieManager.Endpoint.Controllers 5 | { 6 | [Route("api/[controller]")] 7 | [ApiController] 8 | public class GenreController : ControllerBase 9 | { 10 | private string notFoundMessage = "No genres found!"; 11 | private string badRequestMessage = "Value cannot be null!"; 12 | private GenreService _genreService; 13 | 14 | public GenreController(GenreService genreService) 15 | { 16 | _genreService = genreService; 17 | } 18 | 19 | [HttpGet] 20 | [Route("/genres")] 21 | public ActionResult GetAll() 22 | { 23 | var genres = _genreService.GetAll(); 24 | if (genres.Count > 0) 25 | { 26 | return Ok(genres); 27 | } 28 | return NotFound(notFoundMessage); 29 | } 30 | 31 | [HttpGet] 32 | [Route("/genres/{searchString}")] 33 | public ActionResult Get(string searchString) 34 | { 35 | var genres = _genreService.Get(searchString); 36 | if (genres.Count > 0) 37 | { 38 | return Ok(genres); 39 | } 40 | return NotFound(notFoundMessage); 41 | } 42 | 43 | [HttpGet] 44 | [Route("/genres/names")] 45 | public ActionResult GetAllNames() 46 | { 47 | var genres = _genreService.GetAllNames(); 48 | if (genres.Count > 0) 49 | { 50 | return Ok(genres); 51 | } 52 | return NotFound(notFoundMessage); 53 | } 54 | 55 | [HttpGet] 56 | [Route("/genres/like")] 57 | public ActionResult GetLikedGenres() 58 | { 59 | var genres = _genreService.GetLikedGenres(); 60 | if (genres.Count > 0) 61 | { 62 | return Ok(genres); 63 | } 64 | return NotFound(notFoundMessage); 65 | } 66 | 67 | [HttpPut] 68 | [Route("/genres/like/{genreName}")] 69 | public ActionResult LikeActor(string genreName) 70 | { 71 | if (string.IsNullOrEmpty(genreName)) 72 | { 73 | return BadRequest(badRequestMessage); 74 | } 75 | return Ok(_genreService.LikeGenre(genreName)); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /MovieManager.Endpoint/Controllers/ImageController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using MovieManager.BusinessLogic; 3 | using MovieManager.ClassLibrary.RequestBody; 4 | 5 | namespace MovieManager.Endpoint.Controllers 6 | { 7 | [ApiController] 8 | [Route("[controller]")] 9 | public class ImageController : Controller 10 | { 11 | private string notFoundMessage = "No Image found!"; 12 | private MovieService _movieService; 13 | private ActorService _actorService; 14 | 15 | public ImageController(MovieService movieService 16 | , ActorService actorService) 17 | { 18 | _movieService = movieService; 19 | _actorService = actorService; 20 | } 21 | 22 | [HttpPost] 23 | [Route("/images/getimage")] 24 | public IActionResult GetImage([FromBody] ImageRequest imageRequest) 25 | { 26 | var path = ""; 27 | if (imageRequest.ImageType < 10) 28 | { 29 | path = _movieService.GetImagePath(imageRequest); 30 | } 31 | else if (imageRequest.ImageType >= 10) 32 | { 33 | path = _actorService.GetImagePath(imageRequest); 34 | } 35 | if (string.IsNullOrEmpty(path)) 36 | { 37 | return NotFound(notFoundMessage); 38 | } 39 | var image = System.IO.File.OpenRead(path); 40 | return File(image, "image/jpeg"); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MovieManager.Endpoint/Controllers/MovieController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using MovieManager.BusinessLogic; 3 | using MovieManager.ClassLibrary; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | 7 | namespace MovieManager.Endpoint.Controllers 8 | { 9 | [ApiController] 10 | [Route("[controller]")] 11 | public class MovieController : ControllerBase 12 | { 13 | private string badRequestMessage = "Value cannot be null!"; 14 | private string notFoundMessage = "No Movies found!"; 15 | private MovieService _movieService; 16 | private XmlProcessor _xmlProcessor; 17 | private UserSettingsService _config; 18 | 19 | public MovieController( 20 | MovieService movieService, 21 | XmlProcessor xmlProcessor, 22 | UserSettingsService config) 23 | { 24 | _movieService = movieService; 25 | _xmlProcessor = xmlProcessor; 26 | _config = config; 27 | } 28 | 29 | [HttpGet] 30 | [Route("/movies")] 31 | public ActionResult Get() 32 | { 33 | var movies = _movieService.GetMovies(); 34 | if(movies.Count > 0) 35 | { 36 | return Ok(movies); 37 | } 38 | return NotFound(notFoundMessage); 39 | } 40 | 41 | [HttpGet] 42 | [Route("/movies/recent")] 43 | public ActionResult GetMostRecentMovies() 44 | { 45 | var movies = _movieService.GetMostRecentMovies(); 46 | if (movies.Count > 0) 47 | { 48 | return Ok(movies); 49 | } 50 | return NotFound(notFoundMessage); 51 | } 52 | 53 | [HttpGet] 54 | [Route("/movies/years")] 55 | public ActionResult GetYears() 56 | { 57 | var years = _movieService.GetMovieYears(); 58 | if (years.Count > 0) 59 | { 60 | return Ok(years); 61 | } 62 | return NotFound(notFoundMessage); 63 | } 64 | 65 | [HttpPost] 66 | [Route("/movies/filters")] 67 | public ActionResult GetMoviesByFilters([FromBody]FilterRequest filterRequest) 68 | { 69 | if(filterRequest == null) 70 | { 71 | return BadRequest(badRequestMessage); 72 | } 73 | var movies = _movieService.GetMoviesByFilters(filterRequest.FilterType, filterRequest.Filters, filterRequest.IsAndOperator); 74 | if (movies.Count > 0) 75 | { 76 | return Ok(movies); 77 | } 78 | return NotFound(notFoundMessage); 79 | } 80 | 81 | [HttpPost] 82 | [Route("/movies/wildcard")] 83 | public ActionResult GetMoviesByWildcardSearch([FromBody] SearchRequest searchRequest) 84 | { 85 | if (searchRequest == null) 86 | { 87 | return BadRequest(badRequestMessage); 88 | } 89 | var movies = _movieService.GetMoviesWildcard(searchRequest); 90 | if (movies.Count > 0) 91 | { 92 | return Ok(movies); 93 | } 94 | return NotFound(notFoundMessage); 95 | } 96 | 97 | [HttpPost] 98 | [Route("/movies/querySearch")] 99 | public ActionResult GetMoviesByQuery([FromBody] SearchRequest searchRequest) 100 | { 101 | if (searchRequest == null) 102 | { 103 | return BadRequest(searchRequest); 104 | } 105 | var movies = _movieService.GetMoviesByQuery(searchRequest.SearchString); 106 | if (movies.Count > 0) 107 | { 108 | return Ok(movies); 109 | } 110 | return NotFound(notFoundMessage); 111 | } 112 | 113 | [HttpGet] 114 | [Route("/movies/like")] 115 | public ActionResult GetLikedMovies() 116 | { 117 | var movies = _movieService.GetLikedMovies(); 118 | if (movies.Count > 0) 119 | { 120 | return Ok(movies); 121 | } 122 | return NotFound(notFoundMessage); 123 | } 124 | 125 | [HttpPut] 126 | [Route("/movies/like/{imdbId}")] 127 | public ActionResult LikeMovie(string imdbId) 128 | { 129 | if(string.IsNullOrEmpty(imdbId)) 130 | { 131 | return BadRequest(badRequestMessage); 132 | } 133 | return Ok(_movieService.LikeMovie(imdbId)); 134 | } 135 | 136 | [HttpPost] 137 | [Route("/movies/details")] 138 | public ActionResult GetMovieDetails([FromBody] MovieViewModel movieViewModel) 139 | { 140 | if (movieViewModel == null) 141 | { 142 | return BadRequest(badRequestMessage); 143 | } 144 | var movie = _movieService.GetMovieDetails(movieViewModel); 145 | if(movie != null) 146 | { 147 | return Ok(movie); 148 | } 149 | return NotFound(notFoundMessage); 150 | } 151 | 152 | [HttpPut] 153 | [Route("/movies/addnew/{days}")] 154 | public async Task AddNewMoviesAsync(int days) 155 | { 156 | var scanner = new FileScanner(_xmlProcessor); 157 | var movieDir = _config.GetUserSettings().MovieDirectory.Split("|"); 158 | var prevMoviesCount = _movieService.GetMoviesCount(); 159 | List newMovies = new List(); 160 | 161 | foreach (var md in movieDir) 162 | { 163 | var m = scanner.ScanFiles(md.Trim(), days); 164 | await _movieService.InsertMovies(m, false, false); 165 | } 166 | return Ok(_movieService.GetMoviesCount() - prevMoviesCount); 167 | } 168 | 169 | [HttpDelete] 170 | [Route("/movies/delete")] 171 | public ActionResult DeleteNotExistMovies() 172 | { 173 | var scanner = new FileScanner(_xmlProcessor); 174 | var movieDir = _config.GetUserSettings().MovieDirectory; 175 | var m = new List(); 176 | foreach (var md in movieDir) 177 | { 178 | m.AddRange(scanner.ScanFilesForImdbId(movieDir)); 179 | } 180 | return Ok(_movieService.DeleteNonExistentMovies(m).Count); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /MovieManager.Endpoint/Controllers/PlayListController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using MovieManager.BusinessLogic; 4 | using MovieManager.ClassLibrary; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.Linq; 9 | using System.Threading.Tasks; 10 | 11 | namespace MovieManager.Endpoint.Controllers 12 | { 13 | [Route("[controller]")] 14 | [ApiController] 15 | public class PlayListController : ControllerBase 16 | { 17 | private string badRequestMessage = "Value cannot be null!"; 18 | private string path = $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}\\AppData\\Roaming\\PotPlayerMini64\\Playlist\\"; 19 | private string potPlayerExe = ""; 20 | private PotPlayerService _potPlayerService; 21 | 22 | public PlayListController(PotPlayerService potPlayerService, UserSettingsService userSettingsService) 23 | { 24 | _potPlayerService = potPlayerService; 25 | potPlayerExe = userSettingsService.GetUserSettings().PotPlayerDirectory; 26 | } 27 | 28 | 29 | [HttpPost] 30 | [Route("/playlist/create/{playListName}")] 31 | public ActionResult CreatePlayList([FromBody] List movies, string playListName) 32 | { 33 | if (movies == null || movies.Count == 0) 34 | { 35 | return BadRequest(badRequestMessage); 36 | } 37 | try 38 | { 39 | _potPlayerService.BuildPlayList(playListName, path, movies); 40 | Process.Start(potPlayerExe); 41 | } 42 | catch (Exception ex) 43 | { 44 | return BadRequest(ex.ToString()); 45 | } 46 | return Ok(); 47 | } 48 | 49 | [HttpPost] 50 | [Route("/playlist/createbyactors/{playListName}")] 51 | public ActionResult CreatePlayListByActors([FromBody] List actors, string playListName) 52 | { 53 | if (actors == null || actors.Count == 0) 54 | { 55 | return BadRequest(badRequestMessage); 56 | } 57 | try 58 | { 59 | _potPlayerService.BuildPlayListByActors(playListName, path, actors); 60 | Process.Start(potPlayerExe); 61 | } 62 | catch (Exception ex) 63 | { 64 | return BadRequest(ex.ToString()); 65 | } 66 | return Ok(); 67 | } 68 | 69 | [HttpPut] 70 | [Route("/playlist/append/default")] 71 | public ActionResult AppendToDefaultPlayList([FromBody] List movies) 72 | { 73 | if (movies == null || movies.Count == 0) 74 | { 75 | return BadRequest(badRequestMessage); 76 | } 77 | try 78 | { 79 | _potPlayerService.BuildPlayList("TempPlayList", path, movies, System.IO.FileMode.Append); 80 | Process.Start(potPlayerExe); 81 | } 82 | catch (Exception ex) 83 | { 84 | return BadRequest(ex.ToString()); 85 | } 86 | return Ok(); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /MovieManager.Endpoint/Controllers/TagController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using MovieManager.BusinessLogic; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace MovieManager.Endpoint.Controllers 10 | { 11 | [Route("api/[controller]")] 12 | [ApiController] 13 | public class TagController : ControllerBase 14 | { 15 | private string notFoundMessage = "No tags found!"; 16 | private string badRequestMessage = "Value cannot be null!"; 17 | private TagService _tagService; 18 | 19 | public TagController(TagService tagService) 20 | { 21 | _tagService = tagService; 22 | } 23 | 24 | [HttpGet] 25 | [Route("/tags")] 26 | public ActionResult GetAll() 27 | { 28 | var tags = _tagService.GetAll(); 29 | if (tags.Count > 0) 30 | { 31 | return Ok(tags); 32 | } 33 | return NotFound(notFoundMessage); 34 | } 35 | 36 | [HttpGet] 37 | [Route("/tags/{searchString}")] 38 | public ActionResult Get(string searchString) 39 | { 40 | var tags = _tagService.Get(searchString); 41 | if (tags.Count > 0) 42 | { 43 | return Ok(tags); 44 | } 45 | return NotFound(notFoundMessage); 46 | } 47 | 48 | [HttpGet] 49 | [Route("/tags/names")] 50 | public ActionResult GetAllNames() 51 | { 52 | var tags = _tagService.GetAllNames(); 53 | if (tags.Count > 0) 54 | { 55 | return Ok(tags); 56 | } 57 | return NotFound(notFoundMessage); 58 | } 59 | 60 | [HttpGet] 61 | [Route("/tags/like")] 62 | public ActionResult GetLikedTags() 63 | { 64 | var tags = _tagService.GetLikedTags(); 65 | if (tags.Count > 0) 66 | { 67 | return Ok(tags); 68 | } 69 | return NotFound(notFoundMessage); 70 | } 71 | 72 | [HttpPut] 73 | [Route("/tags/like/{tagName}")] 74 | public ActionResult LikeActor(string tagName) 75 | { 76 | if (string.IsNullOrEmpty(tagName)) 77 | { 78 | return BadRequest(badRequestMessage); 79 | } 80 | return Ok(_tagService.LikeTag(tagName)); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /MovieManager.Endpoint/Controllers/UserSettingsController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Options; 3 | using MovieManager.BusinessLogic; 4 | using MovieManager.ClassLibrary; 5 | using Newtonsoft.Json; 6 | using System; 7 | using System.IO; 8 | using System.Reflection; 9 | 10 | namespace MovieManager.Endpoint.Controllers 11 | { 12 | [Route("[controller]")] 13 | [ApiController] 14 | public class UserSettingsController : ControllerBase 15 | { 16 | private string badRequestMessage = "Value cannot be null!"; 17 | private string notFoundMessage = "No User Settings found!"; 18 | private UserSettingsService _config; 19 | 20 | public UserSettingsController(UserSettingsService config) 21 | { 22 | _config = config; 23 | } 24 | 25 | [HttpGet] 26 | [Route("/usersettings")] 27 | public ActionResult Get() 28 | { 29 | if(_config == null) 30 | { 31 | return NotFound(notFoundMessage); 32 | } 33 | UserSettings result = _config.GetUserSettings(); 34 | return Ok(result); 35 | } 36 | 37 | [HttpPut] 38 | [Route("/usersettings/update")] 39 | public ActionResult Update([FromBody] UserSettings userSettings) 40 | { 41 | if(userSettings == null) 42 | { 43 | return BadRequest(badRequestMessage); 44 | } 45 | _config.SetUserSettings(userSettings); 46 | return Ok(userSettings); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /MovieManager.Endpoint/MovieManager.Endpoint.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 6 | Exe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Always 33 | 34 | 35 | Always 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /MovieManager.Endpoint/Program.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/MovieManager.Endpoint/Program.cs -------------------------------------------------------------------------------- /MovieManager.Endpoint/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:25852", 8 | "sslPort": 44338 9 | } 10 | }, 11 | "profiles": { 12 | "MovieManager.Endpoint": { 13 | "commandName": "Project", 14 | "launchBrowser": false, 15 | "launchUrl": "", 16 | "applicationUrl": "https://localhost:5100", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /MovieManager.Endpoint/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.FileProviders; 6 | using Microsoft.Extensions.Hosting; 7 | using MovieManager.BusinessLogic; 8 | using System.IO; 9 | 10 | namespace MovieManager.Endpoint 11 | { 12 | public class Startup 13 | { 14 | public Startup(IConfiguration configuration) 15 | { 16 | Configuration = configuration; 17 | } 18 | 19 | public IConfiguration Configuration { get; } 20 | 21 | // This method gets called by the runtime. Use this method to add services to the container. 22 | public void ConfigureServices(IServiceCollection services) 23 | { 24 | services.AddOptions(); 25 | services.AddControllers(); 26 | services.AddTransient(); 27 | services.AddTransient(); 28 | services.AddTransient(); 29 | services.AddTransient(); 30 | services.AddTransient(); 31 | services.AddTransient(); 32 | services.AddTransient(); 33 | services.AddTransient(); 34 | services.AddSingleton(); 35 | } 36 | 37 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 38 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 39 | { 40 | if (env.IsDevelopment()) 41 | { 42 | app.UseDeveloperExceptionPage(); 43 | } 44 | 45 | app.UseHttpsRedirection(); 46 | app.UseStaticFiles(new StaticFileOptions 47 | { 48 | FileProvider = new PhysicalFileProvider( 49 | Path.Combine(env.ContentRootPath, "build")) 50 | }); 51 | app.UseRouting(); 52 | 53 | app.UseAuthorization(); 54 | 55 | app.UseCors(builder => builder 56 | .AllowAnyOrigin() 57 | .AllowAnyMethod() 58 | .AllowAnyHeader()); 59 | 60 | app.UseEndpoints(endpoints => 61 | { 62 | endpoints.MapControllers(); 63 | endpoints.MapFallbackToFile("index.html"); 64 | }); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /MovieManager.Endpoint/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AppSettings": { 10 | "EndpointHost": "5100" 11 | }, 12 | "AllowedHosts": "*" 13 | } 14 | -------------------------------------------------------------------------------- /MovieManager.Endpoint/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AppSettings": { 10 | "EndpointHost": "5100" 11 | }, 12 | "AllowedHosts": "*" 13 | } 14 | -------------------------------------------------------------------------------- /MovieManager.Testing/ActorServiceTest.cs: -------------------------------------------------------------------------------- 1 | using MovieManager.BusinessLogic; 2 | using Serilog; 3 | using Shouldly; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using Xunit; 8 | 9 | namespace MovieManager.Testing 10 | { 11 | public class ActorServiceTest 12 | { 13 | public ActorServiceTest() 14 | { 15 | Log.Logger = new LoggerConfiguration() 16 | .MinimumLevel.Debug() 17 | .WriteTo.Console() 18 | .WriteTo.File($"logs/movieSrv-{DateTime.Now.ToString("yyyyMMddHHmmss")}.txt") 19 | .CreateLogger(); 20 | } 21 | 22 | [Fact] 23 | public void ActorService_GetAll() 24 | { 25 | //var actorSrv = new ActorService(); 26 | //var actors = actorSrv.GetAll(); 27 | //actors.Count.ShouldNotBe(0); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /MovieManager.Testing/DatabaseContextTest.cs: -------------------------------------------------------------------------------- 1 | using MovieManager.ClassLibrary; 2 | using MovieManager.Data; 3 | using Shouldly; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Xunit; 8 | 9 | namespace MovieManager.Testing 10 | { 11 | public class DatabaseContextTest 12 | { 13 | [Fact] 14 | public void GetMovies() 15 | { 16 | var movies = new List(); 17 | using (var context = new DatabaseContext()) 18 | { 19 | movies = context.Movies.Take(10).ToList(); 20 | } 21 | movies.Count.ShouldNotBe(0); 22 | } 23 | 24 | [Fact] 25 | public void CovertDate() 26 | { 27 | using(var context = new DatabaseContext()) 28 | { 29 | var actors = context.Actors.ToList(); 30 | foreach (var actor in actors) 31 | { 32 | if(!string.IsNullOrEmpty(actor.DateofBirth)) 33 | { 34 | var temp = new DateTime(); 35 | if (DateTime.TryParse(actor.DateofBirth, out temp)) 36 | { 37 | actor.DateofBirth = temp.ToString("yyyy-MM-dd"); 38 | } 39 | } 40 | } 41 | context.SaveChanges(); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /MovieManager.Testing/FileScannerTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualBasic.FileIO; 2 | using MovieManager.BusinessLogic; 3 | using MovieManager.Data; 4 | using Serilog; 5 | using Shouldly; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Xml; 11 | using Xunit; 12 | 13 | namespace MovieManager.Testing 14 | { 15 | public class FileScannerTest 16 | { 17 | public FileScannerTest() 18 | { 19 | Log.Logger = new LoggerConfiguration() 20 | .MinimumLevel.Debug() 21 | .WriteTo.Console() 22 | .WriteTo.File($"logs/movieSrv-{DateTime.Now.ToString("yyyyMMddHHmmss")}.txt") 23 | .CreateLogger(); 24 | } 25 | 26 | [Fact] 27 | public void ScanFilesTest() 28 | { 29 | var fileLocation = @""; 30 | var xmlEngine = new XmlProcessor(); 31 | var fileScanner = new FileScanner(xmlEngine); 32 | var movies = fileScanner.ScanFiles(fileLocation, DateTime.Parse("1/1/2022")); 33 | movies.Count().ShouldNotBe(0); 34 | } 35 | 36 | //[Fact] 37 | //public void UpdateArt() 38 | //{ 39 | // var nfos = Directory.GetFiles(@"E:\MyFile\New\有码", "*.nfo", System.IO.SearchOption.AllDirectories) 40 | // .Where(f => File.GetCreationTime(f) >= DateTime.Parse("1/1/2022")).ToList(); 41 | 42 | // foreach (var nfo in nfos) 43 | // { 44 | // var xmlDoc = new XmlDocument(); 45 | // xmlDoc.Load(nfo); 46 | 47 | // var fanart = xmlDoc. 48 | 49 | // var filename = Path.GetFileNameWithoutExtension(nfo); 50 | // var dir = Path.GetDirectoryName(nfo); 51 | // var fanart = Directory.GetFiles(dir, $"{filename}-fanart.jpg").FirstOrDefault(); 52 | // var poster = Directory.GetFiles(dir, $"{filename}-poster.jpg").FirstOrDefault(); 53 | // var thumb = Directory.GetFiles(dir, $"{filename}-thumb.jpg").FirstOrDefault(); 54 | 55 | // fanart = Path.GetFileNameWithoutExtension(fanart); 56 | // poster = Path.GetFileNameWithoutExtension(poster); 57 | // thumb = Path.GetFileNameWithoutExtension(thumb); 58 | // } 59 | //} 60 | 61 | /*[Fact] 62 | public void RenameNfoAndJpg() 63 | { 64 | var movieExtensions = new List { ".avi", ".mp4", ".wmv", ".mkv" }; 65 | var nfos = Directory.GetFiles(@"E:\MyFile\New\有码", "*.nfo", System.IO.SearchOption.AllDirectories) 66 | .Where(f => File.GetCreationTime(f) >= DateTime.Parse("1/1/2022")).ToList(); 67 | var t = new List(); 68 | foreach (var nfo in nfos) 69 | { 70 | try 71 | { 72 | var dir = Path.GetDirectoryName(nfo); 73 | var nfoFilename = Path.GetFileNameWithoutExtension(nfo); 74 | var processedNfoFilename = nfoFilename; 75 | if (nfoFilename.Contains("-C")) 76 | { 77 | processedNfoFilename = nfoFilename.Substring(0, nfoFilename.Length - 2); 78 | } 79 | var movie = Directory.GetFiles(dir, $"{processedNfoFilename}*.*").Where(f => movieExtensions.Any(f.ToLower().EndsWith)).FirstOrDefault(); 80 | var movieFilename = Path.GetFileNameWithoutExtension(movie); 81 | 82 | if (!string.IsNullOrEmpty(movie) && movieFilename != nfoFilename) 83 | { 84 | t.Add(nfo); 85 | var fanart = Directory.GetFiles(dir, $"{nfoFilename}-fanart.jpg").FirstOrDefault(); 86 | var poster = Directory.GetFiles(dir, $"{nfoFilename}-poster.jpg").FirstOrDefault(); 87 | var thumb = Directory.GetFiles(dir, $"{nfoFilename}-thumb.jpg").FirstOrDefault(); 88 | 89 | FileSystem.RenameFile(fanart, $"{movieFilename}-fanart.jpg"); 90 | FileSystem.RenameFile(poster, $"{movieFilename}-poster.jpg"); 91 | FileSystem.RenameFile(thumb, $"{movieFilename}-thumb.jpg"); 92 | FileSystem.RenameFile(nfo, $"{movieFilename}.nfo"); 93 | } 94 | } 95 | catch (Exception ex) 96 | { 97 | Log.Error(ex.ToString() + "\n\r"); 98 | } 99 | } 100 | 101 | }*/ 102 | 103 | /*[Fact] 104 | public void CorrectFilename() 105 | { 106 | var movieExtensions = new List { ".avi", ".mp4", ".wmv", ".mkv" }; 107 | var movies = Directory.GetFiles(@"E:\MyFile\New\有码", "*..*", System.IO.SearchOption.AllDirectories) 108 | .Where(f => movieExtensions.Any(f.ToLower().EndsWith)).ToList(); 109 | var renameMovies = new List(); 110 | foreach (var m in movies) 111 | { 112 | var movieFilename = Path.GetFileNameWithoutExtension(m); 113 | var nfo = Directory.GetFiles(Path.GetDirectoryName(m), $"{movieFilename}.nfo").FirstOrDefault(); 114 | if (String.IsNullOrEmpty(nfo) || Path.GetFileNameWithoutExtension(nfo) != movieFilename) 115 | { 116 | renameMovies.Add(m); 117 | } 118 | } 119 | 120 | foreach (var m in renameMovies) 121 | { 122 | var filename = Path.GetFileNameWithoutExtension(m); 123 | var ext = Path.GetExtension(m); 124 | var correctFilename = filename.Substring(0, filename.Length - 1) + ext; 125 | FileSystem.RenameFile(m, correctFilename); 126 | } 127 | 128 | }*/ 129 | 130 | /*[Fact] 131 | public void DeleteInvalidNfos() 132 | { 133 | var fileLocation = @"E:\MyFile\New\有码\"; 134 | var nfos = Directory.GetFiles(fileLocation, "*.nfo", SearchOption.AllDirectories) 135 | .Where(f => File.GetCreationTime(f) >= DateTime.Now.AddDays(-90)).ToList(); 136 | var invalidNfos = new List(); 137 | var xmlDoc = new XmlDocument(); 138 | foreach (var nfo in nfos) 139 | { 140 | xmlDoc.Load(nfo); 141 | var year = xmlDoc.GetElementsByTagName("year")[0]?.InnerText; 142 | if(string.IsNullOrEmpty(year)) 143 | { 144 | invalidNfos.Add(nfo); 145 | } 146 | } 147 | foreach(var nfo in invalidNfos) 148 | { 149 | File.Delete(nfo); 150 | } 151 | }*/ 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /MovieManager.Testing/MovieManager.Testing.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | all 20 | 21 | 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | all 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /MovieManager.Testing/MovieServiceTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualBasic.FileIO; 2 | using MovieManager.BusinessLogic; 3 | using MovieManager.ClassLibrary; 4 | using MovieManager.Data; 5 | using Serilog; 6 | using Shouldly; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Linq; 11 | using Xunit; 12 | 13 | namespace MovieManager.Testing 14 | { 15 | //public class MovieServiceTest 16 | //{ 17 | // public MovieServiceTest() 18 | // { 19 | // Log.Logger = new LoggerConfiguration() 20 | // .MinimumLevel.Debug() 21 | // .WriteTo.Console() 22 | // .WriteTo.File($"logs/movieSrv-{DateTime.Now.ToString("yyyyMMddHHmmss")}.txt") 23 | // .CreateLogger(); 24 | // } 25 | 26 | // [Fact] 27 | // public async void MovieServiceProcess_All() 28 | // { 29 | // var fileLocation = @"E:\MyFile\New\有码"; 30 | // var xmlEngine = new XmlProcessor(); 31 | // var fileScanner = new FileScanner(xmlEngine); 32 | // var scrapeService = new ScrapeService(); 33 | // var movieSvc = new MovieService(scrapeService); 34 | // var movies = fileScanner.ScanFiles(fileLocation); 35 | // await movieSvc.InsertMovies(movies); 36 | // } 37 | 38 | // [Fact] 39 | // public async void MovieServiceProcess() 40 | // { 41 | // var fileLocations = new List() { @"E:\MyFile\New\有码", @"F:\Library\多P" }; 42 | // var xmlEngine = new XmlProcessor(); 43 | // var fileScanner = new FileScanner(xmlEngine); 44 | // var scrapeService = new ScrapeService(); 45 | // var movieSvc = new MovieService(scrapeService); 46 | // foreach (var f in fileLocations) 47 | // { 48 | // var movies = fileScanner.ScanFiles(f, DateTime.Parse("1/1/2010")); 49 | // await movieSvc.InsertMovies(movies, false, false); 50 | // } 51 | // } 52 | 53 | // [Fact] 54 | // public void GetMovies() 55 | // { 56 | // var scrapeService = new ScrapeService(); 57 | // var movieSvc = new MovieService(scrapeService); 58 | // var movies = movieSvc.GetMovies(); 59 | // movies.Count().ShouldNotBe(0); 60 | // } 61 | 62 | // [Fact] 63 | // public void GetMoviesByActors() 64 | // { 65 | // var scrapeService = new ScrapeService(); 66 | // var movieSvc = new MovieService(scrapeService); 67 | // var actors = new List() { "" }; 68 | // var movies = movieSvc.GetMoviesByFilters(FilterType.Actors, actors, false); 69 | // movies.Count().ShouldNotBe(0); 70 | // } 71 | 72 | // [Fact] 73 | // public void GetMoviesByImdbId() 74 | // { 75 | // var scrapeService = new ScrapeService(); 76 | // var movieSvc = new MovieService(scrapeService); 77 | // var imdbId = "IPX"; 78 | // var movies = movieSvc.GetMoviesWildcard(imdbId); 79 | // movies.Count().ShouldNotBe(0); 80 | // } 81 | 82 | // //[Fact] 83 | // //public void RenameTest() 84 | // //{ 85 | // // using (var context = new DatabaseContext()) 86 | // // { 87 | // // var movieRenames = context.Movies.Select(x => new MovieRename() 88 | // // { 89 | // // ImdbId = x.ImdbId, 90 | // // Title = x.Title, 91 | // // MovieLocations = x.MovieLocation 92 | // // }).ToList(); 93 | // // foreach (var m in movieRenames) 94 | // // { 95 | // // m.Filename = Path.GetFileNameWithoutExtension(m.MovieLocations.Split('|')[0]); 96 | // // m.MovieLocations = m.MovieLocations.Split('|')[0]; 97 | // // } 98 | // // var notMatchedFilenames = movieRenames.Where(x => x.Filename.Length < 15).ToList(); 99 | // // foreach (var file in notMatchedFilenames) 100 | // // { 101 | // // var ext = Path.GetExtension(file.MovieLocations); 102 | // // FileSystem.RenameFile(file.MovieLocations, $"{file.Title}{ext}"); 103 | // // } 104 | 105 | // // //var movies = context.Movies.Where(x => Path.GetFileNameWithoutExtension(x.MovieLocation.Split('|')[0]) != x.ImdbId).ToList(); 106 | // // } 107 | // //} 108 | 109 | // //public class MovieRename 110 | // //{ 111 | // // public string ImdbId { get; set; } 112 | // // public string Title { get; set; } 113 | // // public string Filename { get; set; } 114 | // // public string MovieLocations { get; set; } 115 | // //} 116 | //} 117 | } 118 | -------------------------------------------------------------------------------- /MovieManager.Testing/PotPlayerServiceTest.cs: -------------------------------------------------------------------------------- 1 | using MovieManager.BusinessLogic; 2 | using MovieManager.ClassLibrary; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Xunit; 7 | 8 | namespace MovieManager.Testing 9 | { 10 | public class PotPlayerServiceTest 11 | { 12 | [Fact] 13 | public void BuildPotPlayerPlayListTest() 14 | { 15 | //var scrapeService = new ScrapeService(); 16 | //var movieSrv = new MovieService(scrapeService); 17 | //var potplayerSrv = new PotPlayerService(movieSrv); 18 | //var searchList = new List() { "" }; 19 | //var movieLocations = movieSrv.GetMoviesByFilters(FilterType.Actors, searchList, false).Select(x => x.MovieLocation).ToList(); 20 | //var path = $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}\\AppData\\Roaming\\PotPlayerMini64\\Playlist\\"; 21 | //potplayerSrv.BuildPlayList("Test", path, movieLocations); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /MovieManager.Testing/ScrapeServiceTest.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using Shouldly; 3 | using MovieManager.BusinessLogic; 4 | using System; 5 | using Serilog; 6 | 7 | namespace MovieManager.Testing 8 | { 9 | public class ScrapeServiceTest 10 | { 11 | public ScrapeServiceTest() 12 | { 13 | Log.Logger = new LoggerConfiguration() 14 | .MinimumLevel.Debug() 15 | .WriteTo.Console() 16 | .WriteTo.File($"logs/movieSrv-{DateTime.Now.ToString("yyyyMMddHHmmss")}.txt") 17 | .CreateLogger(); 18 | } 19 | 20 | [Fact] 21 | public void ScrapeActorTest() 22 | { 23 | var scrpeService = new ScrapeService(); 24 | scrpeService.GetActorInformation(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /MovieManager.Testing/XmlEngineTest.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using Shouldly; 3 | using MovieManager.BusinessLogic; 4 | 5 | namespace MovieManager.Testing 6 | { 7 | public class XmlEngineTest 8 | { 9 | [Fact] 10 | public void ParseXmlTest() 11 | { 12 | var fileLocation = @""; 13 | var xmlEngine = new XmlProcessor(); 14 | var movie = xmlEngine.ParseXmlFile(fileLocation); 15 | movie.ShouldNotBeNull(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /MovieManager.TrayApp/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MovieManager.TrayApp/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /MovieManager.TrayApp/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using Hardcodet.Wpf.TaskbarNotification; 2 | using Microsoft.Extensions.Configuration; 3 | using MovieManager.Data; 4 | using MovieManager.Endpoint; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Data.Entity; 8 | using System.Data.SqlTypes; 9 | using System.Diagnostics; 10 | using System.IO; 11 | using System.Linq; 12 | using System.Management; 13 | using System.Text.RegularExpressions; 14 | using System.Threading; 15 | using System.Windows; 16 | using Serilog; 17 | using System.Linq.Expressions; 18 | using System.Windows.Controls; 19 | using MovieManager.BusinessLogic; 20 | using System.Net.Sockets; 21 | using System.Net; 22 | 23 | namespace MovieManager.TrayApp 24 | { 25 | /// 26 | /// Interaction logic for App.xaml 27 | /// 28 | public partial class App : Application 29 | { 30 | private TaskbarIcon notifyIcon; 31 | private Process webAppProcess; 32 | private const string version = "JavMovieManager_07262024_1"; 33 | public void Test() { } 34 | 35 | protected override void OnStartup(StartupEventArgs e) 36 | { 37 | base.OnStartup(e); 38 | 39 | if (IsAnotherInstanceRunning()) 40 | { 41 | MessageBox.Show("程序已在其他进程打开,请在右下角托盘图标打开程序。", "提示", MessageBoxButton.OK, MessageBoxImage.Information); 42 | Shutdown(); 43 | return; 44 | } 45 | 46 | Log.Logger = new LoggerConfiguration() 47 | .MinimumLevel.Debug() 48 | .WriteTo.Console() 49 | .WriteTo.File($"logs/log_{DateTime.Now.ToString("yyyyMMddHHmmss")}.txt", flushToDiskInterval: TimeSpan.FromSeconds(1)) 50 | .CreateLogger(); 51 | 52 | Log.Information($"Application starting up...Current Version: {version}"); 53 | 54 | notifyIcon = (TaskbarIcon)FindResource("NotifyIcon"); 55 | Program.Run(null); 56 | ExecuteCommands(); 57 | } 58 | 59 | protected override void OnExit(ExitEventArgs e) 60 | { 61 | CloseApp(); 62 | base.OnExit(e); 63 | } 64 | 65 | private void ExecuteCommands() 66 | { 67 | var initializingWindow = new InitializingWindow(); 68 | initializingWindow.Show(); 69 | initializingWindow.Hide(); 70 | initializingWindow.Close(); 71 | } 72 | 73 | private void CloseApp(bool forceShutdown = false) 74 | { 75 | var initializingWindow = new InitializingWindow(); 76 | Log.Information("Application is closing..."); 77 | if (notifyIcon != null) 78 | { 79 | notifyIcon.Dispose(); 80 | } 81 | Log.Information("Application is closed."); 82 | Process.GetCurrentProcess().Kill(); 83 | if (forceShutdown) 84 | { 85 | Application.Current.Shutdown(); 86 | } 87 | } 88 | 89 | private bool IsAnotherInstanceRunning() 90 | { 91 | var currentProcess = Process.GetCurrentProcess(); 92 | var processes = Process.GetProcessesByName(currentProcess.ProcessName); 93 | return processes.Any(p => p.Id != currentProcess.Id); 94 | } 95 | 96 | private bool IsPortAvailable(int port) 97 | { 98 | try 99 | { 100 | TcpListener listener = new TcpListener(IPAddress.Any, port); 101 | listener.Start(); 102 | listener.Stop(); 103 | return true; 104 | } 105 | catch (SocketException) 106 | { 107 | return false; 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /MovieManager.TrayApp/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /MovieManager.TrayApp/DelegateCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace MovieManager.TrayApp 5 | { 6 | public class DelegateCommand : ICommand 7 | { 8 | public Action CommandAction { get; set; } 9 | public Func CanExecuteFunc { get; set; } 10 | 11 | public void Execute(object parameter) 12 | { 13 | CommandAction(); 14 | } 15 | 16 | public bool CanExecute(object parameter) 17 | { 18 | return CanExecuteFunc == null || CanExecuteFunc(); 19 | } 20 | 21 | public event EventHandler CanExecuteChanged 22 | { 23 | add { CommandManager.RequerySuggested += value; } 24 | remove { CommandManager.RequerySuggested -= value; } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /MovieManager.TrayApp/InitializingWindow.xaml: -------------------------------------------------------------------------------- 1 |  15 | 16 | 23 | 24 | -------------------------------------------------------------------------------- /MovieManager.TrayApp/InitializingWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Data; 7 | using System.Windows.Documents; 8 | using System.Windows.Input; 9 | using System.Windows.Interop; 10 | using System.Windows.Media; 11 | using System.Windows.Media.Imaging; 12 | using System.Windows.Shapes; 13 | using static System.Net.Mime.MediaTypeNames; 14 | 15 | namespace MovieManager.TrayApp 16 | { 17 | /// 18 | /// Interaction logic for InitializingWindow.xaml 19 | /// 20 | public partial class InitializingWindow : Window 21 | { 22 | public InitializingWindow() 23 | { 24 | InitializeComponent(); 25 | this.Closing += InitializingWindow_Closing; 26 | } 27 | 28 | public void SetText(string text) 29 | { 30 | Text.Text = text; 31 | } 32 | 33 | private void InitializingWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) 34 | { 35 | // Prevent the window from being closed 36 | e.Cancel = true; 37 | } 38 | 39 | protected override void OnSourceInitialized(EventArgs e) 40 | { 41 | base.OnSourceInitialized(e); 42 | var hWnd = new WindowInteropHelper(this).Handle; 43 | HwndSource.FromHwnd(hWnd)?.AddHook(new HwndSourceHook(WindowProc)); 44 | } 45 | 46 | private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) 47 | { 48 | const int WM_SYSCOMMAND = 0x0112; 49 | const int SC_CLOSE = 0xF060; 50 | 51 | if (msg == WM_SYSCOMMAND && (int)wParam == SC_CLOSE) 52 | { 53 | handled = true; // Prevent the window from being closed 54 | } 55 | 56 | return IntPtr.Zero; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /MovieManager.TrayApp/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  4 | -------------------------------------------------------------------------------- /MovieManager.TrayApp/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace MovieManager.TrayApp 4 | { 5 | /// 6 | /// Interaction logic for MainWindow.xaml 7 | /// 8 | public partial class MainWindow : Window 9 | { 10 | public MainWindow() 11 | { 12 | InitializeComponent(); 13 | Hide(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MovieManager.TrayApp/Movie.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/MovieManager.TrayApp/Movie.ico -------------------------------------------------------------------------------- /MovieManager.TrayApp/MovieManager.TrayApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | netcoreapp3.1 6 | true 7 | Movie.ico 8 | 9 | 10 | 11 | 12 | Always 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | PreserveNewest 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /MovieManager.TrayApp/NotifyIconResources.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /MovieManager.TrayApp/NotifyIconViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using MovieManager.BusinessLogic; 3 | using System; 4 | using System.Diagnostics; 5 | using System.Windows; 6 | using System.Windows.Input; 7 | 8 | namespace MovieManager.TrayApp 9 | { 10 | /// 11 | /// Provides bindable properties and commands for the NotifyIcon. In this sample, the 12 | /// view model is assigned to the NotifyIcon in XAML. Alternatively, the startup routing 13 | /// in App.xaml.cs could have created this view model, and assigned it to the NotifyIcon. 14 | /// 15 | public class NotifyIconViewModel 16 | { 17 | public ICommand OpenBrowserCommand 18 | { 19 | get 20 | { 21 | // May need change port when running on new machine 22 | return new DelegateCommand { CommandAction = () => Process.Start("explorer.exe", AppStaticProperties.WebAppHost) }; 23 | } 24 | } 25 | 26 | public ICommand ExitApplicationCommand 27 | { 28 | get 29 | { 30 | return new DelegateCommand 31 | { 32 | CommandAction = () => 33 | { 34 | Application.Current.Shutdown(); 35 | } 36 | }; 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /MovieManager.Web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /MovieManager.Web/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /MovieManager.Web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "movie-manager", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.7.1", 7 | "@emotion/styled": "^11.6.0", 8 | "@material/checkbox": "^13.0.0", 9 | "@mui/material": "^5.2.8", 10 | "@testing-library/jest-dom": "^5.16.1", 11 | "@testing-library/react": "^12.1.2", 12 | "@testing-library/user-event": "^13.5.0", 13 | "antd": "^4.18.3", 14 | "http-server": "^14.1.0", 15 | "react": "^17.0.2", 16 | "react-checkbox-tree": "^1.7.2", 17 | "react-dom": "^17.0.2", 18 | "react-scripts": "5.0.0", 19 | "serve": "^14.2.1", 20 | "web-vitals": "^2.1.3" 21 | }, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app", 31 | "react-app/jest" 32 | ] 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "devDependencies": { 47 | "@ant-design/icons": "^5.3.7" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /MovieManager.Web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/MovieManager.Web/public/favicon.ico -------------------------------------------------------------------------------- /MovieManager.Web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /MovieManager.Web/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/MovieManager.Web/public/logo192.png -------------------------------------------------------------------------------- /MovieManager.Web/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/MovieManager.Web/public/logo512.png -------------------------------------------------------------------------------- /MovieManager.Web/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /MovieManager.Web/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /MovieManager.Web/src/App.css: -------------------------------------------------------------------------------- 1 | #components-layout-demo-top-side-2 .logo { 2 | float: left; 3 | width: 120px; 4 | height: 31px; 5 | margin: 16px 24px 16px 0; 6 | background: rgba(255, 255, 255, 0.3); 7 | } 8 | 9 | .ant-row-rtl #components-layout-demo-top-side-2 .logo { 10 | float: right; 11 | margin: 16px 0 16px 24px; 12 | } 13 | 14 | .side { 15 | background: #fff; 16 | height: 875px; 17 | overflow-y: auto; 18 | } 19 | 20 | .content { 21 | background: #fff; 22 | height: 100%; 23 | overflow-y: hidden; 24 | } 25 | 26 | .ant-layout-sider-children { 27 | height: 100%; 28 | } 29 | 30 | .rct-node { 31 | margin: 2px 2px 0px 25px; 32 | } 33 | 34 | .rct-title { 35 | margin-left: 10px; 36 | word-wrap: break-word; 37 | line-height: normal; 38 | } 39 | 40 | .actor-sidebar-slider { 41 | width: 250px; 42 | margin: 5px 0px 5px 0px !important; 43 | } 44 | 45 | .actor-sidebar-label { 46 | margin: 5px 0px 0px 0px; 47 | font-weight: bold; 48 | } 49 | 50 | .sidebar { 51 | margin-left: 25px; 52 | } 53 | 54 | .like-checkbox { 55 | margin-top: 15px !important; 56 | margin-left: 22px !important; 57 | } -------------------------------------------------------------------------------- /MovieManager.Web/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /MovieManager.Web/src/Constant.js: -------------------------------------------------------------------------------- 1 | export const NAME_TAG_EACH_PAGE = 63 2 | export const MOVIE_CARD_EACH_PAGE_LARGE_SCREEN = 20 3 | export const MOVIE_CARD_EACH_PAGE_SMALL_SCREEN = 4 4 | export const ACTOR_CARD_EACH_PAG = 40 -------------------------------------------------------------------------------- /MovieManager.Web/src/Imgs/NotFound.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/MovieManager.Web/src/Imgs/NotFound.jpg -------------------------------------------------------------------------------- /MovieManager.Web/src/components/ActorViewer.css: -------------------------------------------------------------------------------- 1 | .actor-search-bar { 2 | width: 200px; 3 | } 4 | 5 | .actor-button { 6 | margin: 5px; 7 | width: 250px; 8 | height: 60px; 9 | } 10 | 11 | .actor-list { 12 | margin: 5px; 13 | } 14 | 15 | 16 | .header-element-right { 17 | vertical-align: middle; 18 | margin: 2px 19 | } 20 | 21 | .actor-poster-card { 22 | margin: 5px; 23 | display: inline-block; 24 | width: 190px; 25 | height:250px; 26 | vertical-align: top; 27 | } 28 | 29 | .actor-image { 30 | width: 100%; /* Ensure the image covers the container's width */ 31 | height: 180px; /* Set the desired height of the image */ 32 | object-position: top; /* Align the image to the top */ 33 | object-fit: cover; 34 | 35 | } 36 | 37 | .actor-details .ant-modal-body { 38 | padding: 0; 39 | overflow: hidden; /* Ensure that floating elements are contained within the modal body */ 40 | } 41 | 42 | .left-container { 43 | float: left; 44 | width: 500px; /* Adjust the width as needed */ 45 | box-sizing: border-box; /* Include padding and border in the element's total width and height */ 46 | margin-right: 20px; /* Space between left and right containers */ 47 | margin-left: 20px; 48 | } 49 | 50 | .right-container { 51 | float: left; 52 | box-sizing: border-box; /* Include padding and border in the element's total width and height */ 53 | } 54 | 55 | .actor-details .actor-detail-card { 56 | width: 450px; 57 | height: 600px; 58 | } 59 | 60 | .actor-details .actor-detail-card img { 61 | width: 450px; 62 | height: 600px; 63 | object-fit: cover; /* Ensures the image is scaled and cropped to fit the dimensions */ 64 | display: block; /* Ensures the image is a block element */ 65 | margin: 0 auto; /* Centers the image horizontally */ 66 | } -------------------------------------------------------------------------------- /MovieManager.Web/src/components/ActorViewer.jsx: -------------------------------------------------------------------------------- 1 | import "./ActorViewer.css" 2 | import { useState, forwardRef, useImperativeHandle, useRef, useEffect } from "react"; 3 | import { Pagination, Button, Spin, Modal, Descriptions, Input, message, Card } from 'antd'; 4 | import { HeartFilled, HeartOutlined, PlusCircleOutlined } from '@ant-design/icons'; 5 | import { getActorByName, likeActor, getMoivesByFilter, createPotPlayerPlayListByActors, getActorByNames, getImage } from "../services/DataService"; 6 | import MovieViewer from "./MovieViewer"; 7 | import { ACTOR_CARD_EACH_PAG as ACTOR_CARD_EACH_PAGE } from "../Constant"; 8 | 9 | const { Search } = Input; 10 | const { Meta } = Card; 11 | 12 | const ActorViewer = forwardRef((props, ref) => { 13 | const [minValue, setMinValue] = useState(0); 14 | const [maxValue, setMaxValue] = useState(ACTOR_CARD_EACH_PAGE); 15 | const [currentPage, setCurrentPage] = useState(-1); 16 | const [actorNames, setActorNames] = useState([]); 17 | const [actor, setActor] = useState(null); 18 | const [currentPageActorCardDetails, setCurrentPageActorCardDetails] = useState([]) 19 | const [isLoading, setIsLoading] = useState(true); 20 | const [visible, setVisible] = useState(false); 21 | const [isLikeLoading, setIsLikeLoading] = useState(false); 22 | const [likeFlag, setLikeFlag] = useState(0); 23 | 24 | const movieViewer = useRef(); 25 | const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 26 | 27 | useImperativeHandle(ref, () => ({ 28 | initializeActors(actorNames) { 29 | resetViewer(); 30 | init(actorNames); 31 | }, 32 | setIsLoading() { 33 | setIsLoading(true); 34 | } 35 | })); 36 | 37 | useEffect(() => { 38 | const fetchActorDetails = async () => { 39 | if (actorNames.length > 0) { 40 | setIsLoading(true); 41 | try { 42 | const resp = await getActorByNames(actorNames.slice(minValue, maxValue)); 43 | setCurrentPageActorCardDetails(resp); 44 | } catch (error) { 45 | console.log(error); 46 | } finally { 47 | setIsLoading(false); 48 | } 49 | } 50 | }; 51 | 52 | fetchActorDetails(); 53 | }, [actorNames, minValue, maxValue]); 54 | 55 | function resetViewer() { 56 | setCurrentPage(-1); 57 | setMinValue(0); 58 | setMaxValue(ACTOR_CARD_EACH_PAGE); 59 | setActorNames([]); 60 | setActor(null); 61 | setCurrentPageActorCardDetails([]); 62 | setVisible(false); 63 | setIsLikeLoading(false); 64 | setLikeFlag(0); 65 | } 66 | 67 | function init(actorNames) { 68 | setMinValue(0); 69 | setActorNames(actorNames); 70 | setCurrentPage(1); 71 | } 72 | 73 | function handleChange(value) { 74 | setMinValue((value - 1) * ACTOR_CARD_EACH_PAGE); 75 | setMaxValue(value * ACTOR_CARD_EACH_PAGE); 76 | setCurrentPage(value); 77 | }; 78 | 79 | function showActorDetails(actorIndex) { 80 | getActorByName(currentPageActorCardDetails[actorIndex].name).then(resp => { 81 | setActor(resp[0]); 82 | setVisible(true); 83 | movieViewer?.current.setIsLoading(); 84 | setLikeFlag(resp[0].liked); 85 | getMoivesByFilter(0, [resp[0].name], false).then(resp => { 86 | movieViewer?.current.initializeMovies(resp, 5, currentPageActorCardDetails[actorIndex].name); 87 | }); 88 | }).catch((error) => { 89 | console.log(error); 90 | }); 91 | } 92 | 93 | async function onSearch(value) { 94 | setIsLoading(true); 95 | try { 96 | const resp = await getActorByName(value); 97 | const actors = resp ? resp.map(x => x.name) : []; 98 | resetViewer() 99 | await sleep(1000); 100 | init(actors); 101 | } catch (error) { 102 | console.log(error); 103 | } finally { 104 | setIsLoading(false); 105 | } 106 | } 107 | 108 | function onLikeClick() { 109 | setIsLikeLoading(true); 110 | likeActor(actor?.name).then(resp => { 111 | setIsLikeLoading(false); 112 | setLikeFlag(resp); 113 | }).catch(error => console.log(error)); 114 | } 115 | 116 | function createPotPlayList() { 117 | let actorslist = []; 118 | for (let i = 0; i < actorNames.length; ++i) { 119 | actorslist.push(actorNames[i]); 120 | } 121 | createPotPlayerPlayListByActors(actorslist, "Selected Actors").then(() => { 122 | message.info("加入完毕"); 123 | }).catch((error) => { 124 | console.log(error); 125 | message.info("加入失败!"); 126 | }); 127 | } 128 | 129 | return ( 130 |
131 | {isLoading ?
: 132 | } 140 |
141 | 142 | 150 |
151 | {isLoading ?
: 152 |
153 |
154 | {currentPageActorCardDetails?.map((actor, i) => 155 | showActorDetails(i)} 160 | cover={} 161 | > 162 | 163 | )} 164 |
165 |
} 166 | : } 170 | onClick={onLikeClick} 171 | loading={isLikeLoading}>]} 172 | centered 173 | visible={visible} 174 | onOk={() => setVisible(false)} 175 | onCancel={() => setVisible(false)} 176 | width={1600} 177 | className="actor-details" 178 | > 179 |
180 | } 183 | className="actor-detail-card" 184 | > 185 |
186 |
187 | 188 | {actor?.name} 189 | {actor?.dateofBirth} 190 | {actor?.height} 191 | {actor?.cup} 192 | {actor?.bust ?? "?"} cm 193 | {actor?.waist ?? "?"} cm 194 | {actor?.hips ?? "?"} cm 195 | {actor?.looks ?? "?"} 分 196 | {actor?.body ?? "?"} 分 197 | {actor?.sexAppeal ?? "?"} 分 198 | {actor?.overall ?? "?"} 分 199 | 200 | 201 |
202 |
203 |
204 | ) 205 | }); 206 | 207 | const ImageLoader = ({ type, id }) => { 208 | const [imageSrc, setImageSrc] = useState(null); 209 | 210 | useEffect(() => { 211 | const fetchImage = async () => { 212 | const src = await getImage(type, id); 213 | setImageSrc(src); 214 | }; 215 | fetchImage(); 216 | }, [type, id]); 217 | 218 | return imageSrc ? : ; 219 | }; 220 | 221 | export default ActorViewer; -------------------------------------------------------------------------------- /MovieManager.Web/src/components/DirectorViewer.css: -------------------------------------------------------------------------------- 1 | .director-search-bar { 2 | width: 200px; 3 | } 4 | 5 | .director-button { 6 | margin: 5px; 7 | width: 250px; 8 | height: 60px; 9 | } 10 | 11 | .director-span { 12 | display: -webkit-box; 13 | -webkit-box-orient: vertical; 14 | -webkit-line-clamp: 1; 15 | overflow: hidden; 16 | width: 220px; 17 | text-overflow: ellipsis; 18 | } 19 | 20 | .director-list { 21 | margin: 5px; 22 | } -------------------------------------------------------------------------------- /MovieManager.Web/src/components/DirectorViewer.jsx: -------------------------------------------------------------------------------- 1 | import "./DirectorViewer.css" 2 | import { useState, forwardRef, useImperativeHandle, useRef } from "react"; 3 | import { Pagination, Button, Spin, Modal, Descriptions, Input } from 'antd'; 4 | import { getMoivesByFilter } from "../services/DataService"; 5 | import MovieViewer from "./MovieViewer"; 6 | 7 | const directorViewer = forwardRef((props, ref) => { 8 | const numEachPage = 63; 9 | 10 | const [minValue, setMinValue] = useState(0); 11 | const [maxValue, setMaxValue] = useState(numEachPage); 12 | const [directors, setDirectors] = useState([]); 13 | const [director, setDirector] = useState(null); 14 | const [isLoading, setIsLoading] = useState(true); 15 | const [visible, setVisible] = useState(false); 16 | 17 | const movieViewer = useRef(); 18 | 19 | useImperativeHandle(ref, () => ({ 20 | initializeDirectors(directors) { 21 | init(directors); 22 | }, 23 | setIsLoading() { 24 | setIsLoading(true); 25 | } 26 | })); 27 | 28 | function init(directors) { 29 | setMinValue(0); 30 | setMaxValue(numEachPage); 31 | setDirectors(directors); 32 | setIsLoading(false); 33 | } 34 | 35 | function handleChange(value) { 36 | setMinValue((value - 1) * numEachPage); 37 | setMaxValue(value * numEachPage); 38 | }; 39 | 40 | function showDirectorDetails(directorIndex) { 41 | setDirector(directors[directorIndex]); 42 | setVisible(true); 43 | // movieViewer?.current.setIsLoading(); 44 | getMoivesByFilter(3, [directors[directorIndex]], false).then(resp => { 45 | movieViewer?.current.initializeMovies(resp, 5, directors[directorIndex]); 46 | }); 47 | } 48 | 49 | // function onSearch(value) { 50 | // setIsLoading(true); 51 | // getdirectorByName(value).then(resp => { 52 | // let directors = resp ? resp.map(x => x.name) : []; 53 | // init(directors); 54 | // }).catch(error => console.log(error)); 55 | // } 56 | 57 | return ( 58 |
59 | {isLoading ?
: 60 | } 68 | {/* */} 69 | {isLoading ?
: 70 |
71 |
72 | {directors?.slice(minValue, maxValue).map((director, i) => 73 | )} 76 |
77 |
} 78 | ]} 81 | centered 82 | visible={visible} 83 | onOk={() => setVisible(false)} 84 | onCancel={() => setVisible(false)} 85 | width={1100} 86 | className="director-details" 87 | > 88 | 89 | 90 | 91 | 92 |
93 | ) 94 | }); 95 | export default directorViewer; -------------------------------------------------------------------------------- /MovieManager.Web/src/components/GenreViewer.css: -------------------------------------------------------------------------------- 1 | .genre-search-bar { 2 | width: 200px; 3 | } 4 | 5 | .genre-button { 6 | margin: 5px; 7 | width: 250px; 8 | height: 60px; 9 | } 10 | 11 | .genre-span { 12 | display: -webkit-box; 13 | -webkit-box-orient: vertical; 14 | -webkit-line-clamp: 1; 15 | overflow: hidden; 16 | width: 220px; 17 | text-overflow: ellipsis; 18 | } 19 | 20 | .genre-list { 21 | margin: 5px; 22 | } -------------------------------------------------------------------------------- /MovieManager.Web/src/components/GenreViewer.jsx: -------------------------------------------------------------------------------- 1 | import "./GenreViewer.css" 2 | import { useState, forwardRef, useImperativeHandle, useRef } from "react"; 3 | import { Pagination, Button, Spin, Modal, Descriptions, Input } from 'antd'; 4 | import { HeartFilled, HeartOutlined } from '@ant-design/icons'; 5 | import { getMoivesByFilter, getGenreByName, likeGenre } from "../services/DataService"; 6 | import MovieViewer from "./MovieViewer"; 7 | import { MOVIE_CARD_EACH_PAGE_SMALL_SCREEN } from "../Constant"; 8 | 9 | const { Search } = Input; 10 | 11 | const GenreViewer = forwardRef((props, ref) => { 12 | const numEachPage = 63; 13 | 14 | const [minValue, setMinValue] = useState(0); 15 | const [maxValue, setMaxValue] = useState(numEachPage); 16 | const [genres, setGenres] = useState([]); 17 | const [genre, setGenre] = useState(null); 18 | const [isLoading, setIsLoading] = useState(true); 19 | const [visible, setVisible] = useState(false); 20 | const [isLikeLoading, setIsLikeLoading] = useState(false); 21 | const [likeFlag, setLikeFlag] = useState(0); 22 | 23 | const movieViewer = useRef(); 24 | 25 | useImperativeHandle(ref, () => ({ 26 | initializeGenres(genres) { 27 | init(genres); 28 | }, 29 | setIsLoading() { 30 | setIsLoading(true); 31 | } 32 | })); 33 | 34 | function init(genres) { 35 | setMinValue(0); 36 | setMaxValue(numEachPage); 37 | setGenres(genres); 38 | setIsLoading(false); 39 | } 40 | 41 | function handleChange(value) { 42 | setMinValue((value - 1) * numEachPage); 43 | setMaxValue(value * numEachPage); 44 | }; 45 | 46 | function showGenreDetails(genreIndex) { 47 | getGenreByName(genres[genreIndex]).then(resp => { 48 | setGenre(resp[0]); 49 | setVisible(true); 50 | movieViewer?.current.setIsLoading(); 51 | setLikeFlag(resp[0].liked); 52 | getMoivesByFilter(1, [genres[genreIndex]], false).then(resp => { 53 | movieViewer?.current.initializeMovies(resp, MOVIE_CARD_EACH_PAGE_SMALL_SCREEN, genres[genreIndex]); 54 | }); 55 | }).catch((error) => { 56 | console.log(error); 57 | }); 58 | } 59 | 60 | function onSearch(value) { 61 | setIsLoading(true); 62 | getGenreByName(value).then(resp => { 63 | let genres = resp ? resp.map(x => x.name) : []; 64 | init(genres); 65 | }).catch(error => console.log(error)); 66 | } 67 | 68 | function onLikeClick() { 69 | setIsLikeLoading(true); 70 | likeGenre(genre?.name).then(resp => { 71 | setIsLikeLoading(false); 72 | setLikeFlag(resp); 73 | }).catch(error => console.log(error)); 74 | } 75 | 76 | return ( 77 |
78 | {isLoading ?
: 79 | } 87 | 88 | {isLoading ?
: 89 |
90 |
91 | {genres?.slice(minValue, maxValue).map((genre, i) => 92 | )} 95 |
96 |
} 97 | : } 101 | onClick={onLikeClick} 102 | loading={isLikeLoading}>]} 103 | centered 104 | visible={visible} 105 | onOk={() => setVisible(false)} 106 | onCancel={() => setVisible(false)} 107 | width={1100} 108 | className="genre-details" 109 | > 110 | 111 | 112 | 113 | 114 |
115 | ) 116 | }); 117 | export default GenreViewer; -------------------------------------------------------------------------------- /MovieManager.Web/src/components/MovieViewer.css: -------------------------------------------------------------------------------- 1 | .ant-descriptions-item-label { 2 | padding: 4px 8px !important; 3 | } 4 | 5 | .ant-descriptions-item-content { 6 | padding: 4px 8px !important; 7 | } 8 | 9 | .poster-card { 10 | margin: 5px; 11 | display: inline-block; 12 | width: 190px; 13 | height: 400px; 14 | vertical-align: top; 15 | } 16 | 17 | .fanart-card { 18 | margin: 5px; 19 | width: 600px; 20 | height: 402px; 21 | vertical-align: top; 22 | } 23 | 24 | .poster-image { 25 | /* width: 240px; 26 | height: 350px; */ 27 | width: 180px; 28 | height: 262px; 29 | margin-left: auto; 30 | margin-right: auto; 31 | } 32 | 33 | .fanart-image { 34 | width: 600px; 35 | height: 402px; 36 | margin-left: auto; 37 | margin-right: auto; 38 | } 39 | 40 | .movie-list { 41 | margin-top: 10px; 42 | } 43 | 44 | .header-left { 45 | display: inline-block; 46 | vertical-align: middle; 47 | margin-top: 10px; 48 | } 49 | 50 | .header-right { 51 | margin-right: 48px; 52 | margin-top: 3px; 53 | display: inline-block; 54 | float: right; 55 | } 56 | 57 | .ant-card-meta-description { 58 | display: -webkit-box; 59 | -webkit-box-orient: vertical; 60 | -webkit-line-clamp: 3; 61 | overflow: hidden; 62 | } 63 | 64 | .header-element-right { 65 | vertical-align: middle; 66 | margin: 2px 67 | } 68 | 69 | .modal-button { 70 | margin: 2px; 71 | } 72 | 73 | .movie-search-bar { 74 | width: 200px; 75 | } -------------------------------------------------------------------------------- /MovieManager.Web/src/components/SettingsViewer.css: -------------------------------------------------------------------------------- 1 | .input { 2 | width: 300px; 3 | } 4 | 5 | .settings { 6 | margin: 10px; 7 | } 8 | 9 | .dynamic-delete-button { 10 | cursor: pointer; 11 | position: relative; 12 | top: 4px; 13 | font-size: 24px; 14 | color: #999; 15 | transition: all .3s; 16 | } 17 | .dynamic-delete-button:hover { 18 | color: #777; 19 | } 20 | .dynamic-delete-button[disabled] { 21 | cursor: not-allowed; 22 | opacity: 0.5; 23 | } -------------------------------------------------------------------------------- /MovieManager.Web/src/components/TagViewer.css: -------------------------------------------------------------------------------- 1 | .tag-search-bar { 2 | width: 200px; 3 | } 4 | 5 | .tag-button { 6 | margin: 5px; 7 | width: 250px; 8 | height: 60px; 9 | } 10 | 11 | .tag-span { 12 | display: -webkit-box; 13 | -webkit-box-orient: vertical; 14 | -webkit-line-clamp: 1; 15 | overflow: hidden; 16 | width: 220px; 17 | text-overflow: ellipsis; 18 | } 19 | 20 | .tag-list { 21 | margin: 5px; 22 | } -------------------------------------------------------------------------------- /MovieManager.Web/src/components/TagViewer.jsx: -------------------------------------------------------------------------------- 1 | import "./TagViewer.css" 2 | import { useState, forwardRef, useImperativeHandle, useRef } from "react"; 3 | import { Pagination, Button, Spin, Modal, Descriptions, Input } from 'antd'; 4 | import { HeartFilled, HeartOutlined } from '@ant-design/icons'; 5 | import { getMoivesByFilter, getTagByName, likeTag } from "../services/DataService"; 6 | import MovieViewer from "./MovieViewer"; 7 | import { MOVIE_CARD_EACH_PAGE_SMALL_SCREEN } from "../Constant"; 8 | 9 | const { Search } = Input; 10 | 11 | const TagViewer = forwardRef((props, ref) => { 12 | const numEachPage = 63; 13 | 14 | const [minValue, setMinValue] = useState(0); 15 | const [maxValue, setMaxValue] = useState(numEachPage); 16 | const [tags, setTags] = useState([]); 17 | const [tag, setTag] = useState(null); 18 | const [isLoading, setIsLoading] = useState(true); 19 | const [visible, setVisible] = useState(false); 20 | const [isLikeLoading, setIsLikeLoading] = useState(false); 21 | const [likeFlag, setLikeFlag] = useState(0); 22 | 23 | const movieViewer = useRef(); 24 | 25 | useImperativeHandle(ref, () => ({ 26 | initializeTags(tags) { 27 | init(tags); 28 | }, 29 | setIsLoading() { 30 | setIsLoading(true); 31 | } 32 | })); 33 | 34 | function init(tags) { 35 | setMinValue(0); 36 | setMaxValue(numEachPage); 37 | setTags(tags); 38 | setIsLoading(false); 39 | } 40 | 41 | function handleChange(value) { 42 | setMinValue((value - 1) * numEachPage); 43 | setMaxValue(value * numEachPage); 44 | }; 45 | 46 | function showTagDetails(tagIndex) { 47 | getTagByName(tags[tagIndex]).then(resp => { 48 | setTag(resp[0]); 49 | setVisible(true); 50 | movieViewer?.current.setIsLoading(); 51 | setLikeFlag(resp[0].liked); 52 | getMoivesByFilter(1, [tags[tagIndex]], false).then(resp => { 53 | movieViewer?.current.initializeMovies(resp, MOVIE_CARD_EACH_PAGE_SMALL_SCREEN, tags[tagIndex]); 54 | }); 55 | }).catch((error) => { 56 | console.log(error); 57 | }); 58 | } 59 | 60 | function onSearch(value) { 61 | setIsLoading(true); 62 | getTagByName(value).then(resp => { 63 | let tags = resp ? resp.map(x => x.name) : []; 64 | init(tags); 65 | }).catch(error => console.log(error)); 66 | } 67 | 68 | function onLikeClick() { 69 | setIsLikeLoading(true); 70 | likeTag(tag?.name).then(resp => { 71 | setIsLikeLoading(false); 72 | setLikeFlag(resp); 73 | }).catch(error => console.log(error)); 74 | } 75 | 76 | return ( 77 |
78 | {isLoading ?
: 79 | } 87 | 88 | {isLoading ?
: 89 |
90 |
91 | {tags?.slice(minValue, maxValue).map((tag, i) => 92 | )} 95 |
96 |
} 97 | : } 101 | onClick={onLikeClick} 102 | loading={isLikeLoading}>]} 103 | centered 104 | visible={visible} 105 | onOk={() => setVisible(false)} 106 | onCancel={() => setVisible(false)} 107 | width={1100} 108 | className="tag-details" 109 | > 110 | 111 | 112 | 113 | 114 |
115 | ) 116 | }); 117 | export default TagViewer; -------------------------------------------------------------------------------- /MovieManager.Web/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /MovieManager.Web/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | , 9 | document.getElementById('root') 10 | ); 11 | 12 | // If you want to start measuring performance in your app, pass a function 13 | // to log results (for example: reportWebVitals(console.log)) 14 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 15 | reportWebVitals(); 16 | -------------------------------------------------------------------------------- /MovieManager.Web/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MovieManager.Web/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /MovieManager.Web/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /MovieManager.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.9.34607.119 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MovieManager.ClassLibrary", "MovieManager.ClassLibrary\MovieManager.ClassLibrary.csproj", "{96C1446A-360C-46E0-9B15-AD633F200627}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{39B09C08-005F-4563-A8FD-D0133134842F}" 9 | ProjectSection(SolutionItems) = preProject 10 | .editorconfig = .editorconfig 11 | EndProjectSection 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MovieManager.Data", "MovieManager.Data\MovieManager.Data.csproj", "{7AD9DDB3-E0DB-454F-BC15-5AFBBDD75C29}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MovieManager.Testing", "MovieManager.Testing\MovieManager.Testing.csproj", "{98F26E49-13A8-4A03-B7B6-4726B6A73FA4}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MovieManager.BusinessLogic", "MovieManager.BusinessLogic\MovieManager.BusinessLogic.csproj", "{7C0F9CE0-B5F0-4831-B2E1-C297B1DDC4F6}" 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MovieManager.Endpoint", "MovieManager.Endpoint\MovieManager.Endpoint.csproj", "{A74CE835-3F30-4F50-9EFE-0344F9064812}" 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MovieManager.TrayApp", "MovieManager.TrayApp\MovieManager.TrayApp.csproj", "{96F51795-F2ED-4E5A-9FB0-324F19B13E26}" 22 | EndProject 23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MovieManager.Deployment", "MovieManager.Deployment\MovieManager.Deployment.csproj", "{D6D1A8AA-F561-420E-B728-9CDDE495BB1D}" 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Debug|Any CPU = Debug|Any CPU 28 | Release|Any CPU = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 31 | {96C1446A-360C-46E0-9B15-AD633F200627}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {96C1446A-360C-46E0-9B15-AD633F200627}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {96C1446A-360C-46E0-9B15-AD633F200627}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {96C1446A-360C-46E0-9B15-AD633F200627}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {7AD9DDB3-E0DB-454F-BC15-5AFBBDD75C29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {7AD9DDB3-E0DB-454F-BC15-5AFBBDD75C29}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {7AD9DDB3-E0DB-454F-BC15-5AFBBDD75C29}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {7AD9DDB3-E0DB-454F-BC15-5AFBBDD75C29}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {98F26E49-13A8-4A03-B7B6-4726B6A73FA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {98F26E49-13A8-4A03-B7B6-4726B6A73FA4}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {98F26E49-13A8-4A03-B7B6-4726B6A73FA4}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {98F26E49-13A8-4A03-B7B6-4726B6A73FA4}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {7C0F9CE0-B5F0-4831-B2E1-C297B1DDC4F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {7C0F9CE0-B5F0-4831-B2E1-C297B1DDC4F6}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {7C0F9CE0-B5F0-4831-B2E1-C297B1DDC4F6}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {7C0F9CE0-B5F0-4831-B2E1-C297B1DDC4F6}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {A74CE835-3F30-4F50-9EFE-0344F9064812}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {A74CE835-3F30-4F50-9EFE-0344F9064812}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {A74CE835-3F30-4F50-9EFE-0344F9064812}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {A74CE835-3F30-4F50-9EFE-0344F9064812}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {96F51795-F2ED-4E5A-9FB0-324F19B13E26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {96F51795-F2ED-4E5A-9FB0-324F19B13E26}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {96F51795-F2ED-4E5A-9FB0-324F19B13E26}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {96F51795-F2ED-4E5A-9FB0-324F19B13E26}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {D6D1A8AA-F561-420E-B728-9CDDE495BB1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {D6D1A8AA-F561-420E-B728-9CDDE495BB1D}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {D6D1A8AA-F561-420E-B728-9CDDE495BB1D}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {D6D1A8AA-F561-420E-B728-9CDDE495BB1D}.Release|Any CPU.Build.0 = Release|Any CPU 59 | EndGlobalSection 60 | GlobalSection(SolutionProperties) = preSolution 61 | HideSolutionNode = FALSE 62 | EndGlobalSection 63 | GlobalSection(ExtensibilityGlobals) = postSolution 64 | SolutionGuid = {F61330F2-C593-4E5E-A051-CA9EBE745B53} 65 | EndGlobalSection 66 | EndGlobal 67 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # 交流群 2 | https://t.me/+9JBjtAz_EqhmMmRh 3 | # 赞助我一杯咖啡 4 | 5 | 6 | # 软件更新方法 7 | 1. 在[发布页面](https://github.com/4evergaeul/JAV_MovieManager/releases/tag/v1.4.3)找到最新版本 8 | 2. 下载**没有NewUser**后缀的zip文件 9 | 3. 解压到本地 10 | 4. 复制MovieDb.db到新版本文件夹 11 | 12 | # 软件介绍(目前暂不支持步兵和欧美片) 13 | 所谓工欲善其事必先利其器,如果说拥有一个元数据完备的电影库则是优雅看片的第一步,那么我认为有一个功能功能完备的电影管理器则是优雅看片的第二步。 14 | 15 | 自从我19,20之际参考@Pockies大佬写的《[利用AV Data Capture+Jellyfin+Kodi打造更优雅的本地AV(毛片)+普通影片媒体库](https://pockies.github.io/2020/01/09/av-data-capture-jellyfin-kodi/)》一文,建立了我的电影库后,我便一直苦于没有一个符合我心目中全部功能的PC端影片管理软件,当时正逢疫情之际,我也得以长期在家办公,于是便心生写一个满足自己想要功能的影音管理器。 16 | 17 | 从21年动笔,到24年今日的第一版release,作为一款轻量级的软件来说开发周期算是很长的了,其实在软件立项1,2个月后基本功能上就已经成型,但归咎于我前端的不熟练,我便一直懒于完成这个软件的使用周期。在23年chatgpt横空出世后,我便利用它完成了整个软件的loop。 18 | 19 | 作为一个传统行业出身的码农,我选的后端技术栈是 .NET Core 3.1,前端用的是React.js; UI框架采用的是Ant Design; 数据库采用的是SQL Lite。 20 | 21 | 软件主要的页面有“影片”, “演员”,其次有类型,标签,导演。我在前两个页面花了比较大的功夫,功能也更加完备。 22 | 23 | 电影页面如下,基本就是以Ant Design里Card来显示各个影片的缩略图,屏幕导航栏可以根据演员,类型等信息快速filter查找影片。 24 | 25 | 右侧是影片搜索,加入Potplayer播放列表功能。并且支持自定义SQL搜索。 26 | ![](Readme/1.jpg) 27 | 28 | 点击影片卡后即可进入影片详情界面,界面会自动获取演员,类型,标签信息并以Button形式显示在详情界面上,点击button即可查看相关影片。 29 | ![](Readme/2.png) 30 | 31 | 演员界面如下,目前的演员列表是基于我的电影收藏刮削获得(本人库收藏6000余部,基本囊括热门女优),在本程序里也内置了一个女演员刮削器。演员界面的左侧支持通过年龄,身高,罩杯进行filter,右上方可以将屏幕内所有演员的电影加入Potplayer播放列表,以及人名搜索功能。 32 | ![](Readme/3.png) 33 | 34 | 点击演员头像卡即可进入演员详情页面,页面里将有演员的各项信息以及评分(评分来源为minnano-av,同时这也是我用于刮削数据的网站) 35 | ![](Readme/4.png) 36 | 37 | # 安装说明 38 | 影片需要预先通过Movie_Data_Capture等刮削器取得元数据和影片封面图片。如果已有元数据,请直接从第二步开始阅读。 39 | ## 第一步:影片刮削 40 | 从7/12/2024开始,推荐刮削器换为[JavSP](https://github.com/Yuukiy/JavSP)。大家可以去原Repo下载,或使用和我配置一样的JavSP。下载链接如下。 41 | https://github.com/4evergaeul/JAV_MovieManager/releases/download/v1.3.1/JavSp.zip 42 | 43 | 1. 清理影片名,让影片名的格式为:{番号名}-{序号} 或 {番号名}-{序号}-C。 44 | 例如xxxx.com@ADN-566.mp4需要重命名为ADN-566.mp4,否则影片刮削器无法识别。 45 | 46 | 2. 重命名完毕后,确认JavSP.exe,config.ini,和将要整理的影片在同一文件夹内。 47 | ![](Readme/Picture5.png) 48 | 49 | 3. 运行JavSP.exe,程序运行完毕后,影片将按照女优名分类放入#整理完成文件夹中。 50 | 51 | 4. 运行[男优刮削器](https://github.com/4evergaeul/AVDanyuScrapper)(AvdanyuScraper.exe,可选),选择#整理完成文件夹,等待刮削结束。 52 | 53 | 此时数据整理完毕,之后可以把处理过后的文件夹全部移动到你的影片库位置。 54 | 55 | ## 第二步:安装软件 56 | 1. 下载安装[.NET SDK](https://download.visualstudio.microsoft.com/download/pr/b70ad520-0e60-43f5-aee2-d3965094a40d/667c122b3736dcbfa1beff08092dbfc3/dotnet-sdk-3.1.426-win-x64.exe), [Potplayer](https://t1.daumcdn.net/potplayer/PotPlayer/Version/Latest/PotPlayerSetup64.exe) 57 | 58 | 2. 运行dotnet-sdk-3.1.426-win-x64.exe,并完成安装。这个是.NET Core 3.1的SDK,也将用于该程序的后端。 59 | 60 | 3. 安装PotPlayerSetup6,建议使用默认安装位置 ```C:\Program Files\``` 以便之后无需重新在软件内配置。 61 | 62 | 4. 在一切安装完毕后,即可以解压MovieManager。双击"MovieManager.TrayApp.exe"运行。此时会弹窗“程序正在初始化”,请等待大约10-30秒。 63 | 64 | 5. 程序初始化完毕后,弹窗会自动关闭,并且程序会用默认浏览器打开。并且程序也会出现在屏幕右下角的托盘图标中,如下图。 65 | ![](Readme/Picture9.png) 66 | 67 | 6. 右键图标,点击"打开"。这个时候默认浏览器会打开影片管理器。点击设置。 68 | ![](Readme/Picture10.png) 69 | 70 | 7. 下面是设置表格的介绍: 71 | (必须) “文件夹”: 设置为你的影片目录(需要手动输入,或把地址复制粘贴过去)。e.g.``` J:\MyFile\New\有码\演员```。
72 | 如果需要测试,可以在文件夹下找到TestingMovieLib,把该文件夹设置为影片目录即可。
73 | 演员头像库请从该库下载:https://github.com/gfriends/gfriends
74 | (可选) “演员头像(DMM)”:将用于演员页面的演员大头照。放文件夹地址。
75 | (可选)“演员头像(全体)”:用于演员详细页面里的写真照片。放文件夹地址。
76 | “扫描所有文件”: 默认为ON,如果OFF的话则可以设定扫描多少天内加的电影。
77 | “扫描多少天内加的电影”:如果这里的数值设为-1,则搜索所有电影。
78 | ![](Readme/Picture11.png) 79 | 80 | 8. 在一切配置完成后,点击“保存设置”->“添加新电影”。 程序就会开始构建影片库,这个时长会根据你影片数量而定。影片库构建完成后页面会刷新并返回电影页面。(如果点击保存设置后,程序未能立刻保存,说明后端程序没有正常运行,右键点击托盘栏图标并点击退出,重新运行程序即可) 81 | 82 | 83 | 配置完成后类似下图。 84 | ![](Readme/Picture12.png) 85 | 86 | 87 | # Repository文档 88 | ### 工程文件树 89 | ``` 90 | MovieManager 91 | │ 92 | ├───MovieManager.BusinessLogic: 后端逻辑 93 | │ 94 | ├───MovieManager.ClassLibrary: ViewClass 95 | │ 96 | ├───MovieManager.Data:数据库交互层 97 | │ 98 | ├───MovieManager.DB:数据库文件 99 | │ 100 | ├───MovieManager.Deployment:用于自动部署,build的executable可在./MovieManager.TrayApp/bin/Any CPU找到 101 | │ 102 | ├───MovieManager.Endpoint: API文件 103 | │ 104 | ├───MovieManager.Testing: Unit Testing文件 105 | │ 106 | ├───MovieManager.TrayApp: 程序Startup逻辑 107 | │ 108 | └───MovieManager.Web: 前端逻辑 109 | ``` 110 | ### Build文件树 111 | ``` 112 | MovieManager_{MMddyyyy}_{hhmmss} 113 | │ 114 | ├───build: 前端文件 115 | │ 116 | ├───logs: 日志文件 117 | │ 118 | ├───TestingMovieLib:测试用的影片库 119 | │ 120 | └────runtimes 121 | 122 | ``` 123 | 124 | ### 参数 125 | 参数位于appsettings.json文件内。下面是各个参数的解释: 126 | - EndpointHost:后端API的端口,如果要更改为其他则需要更改并重新build前端项目。 127 | 128 | ### Loggings 129 | 项目使用的时Serilog的loggings库,log文件build的文件夹下。 130 | 131 | # Road Map 132 | * Display movie/actor cards amount based on screen size 133 | * Genre result one-click add to playlist 134 | * Tag result one-click add to playlist 135 | * Director search 136 | * Actor/Movie card add JabDb/Jable url 137 | -------------------------------------------------------------------------------- /Readme/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Readme/1.jpg -------------------------------------------------------------------------------- /Readme/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Readme/2.png -------------------------------------------------------------------------------- /Readme/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Readme/3.png -------------------------------------------------------------------------------- /Readme/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Readme/4.png -------------------------------------------------------------------------------- /Readme/Picture10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Readme/Picture10.png -------------------------------------------------------------------------------- /Readme/Picture11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Readme/Picture11.png -------------------------------------------------------------------------------- /Readme/Picture12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Readme/Picture12.png -------------------------------------------------------------------------------- /Readme/Picture5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Readme/Picture5.png -------------------------------------------------------------------------------- /Readme/Picture6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Readme/Picture6.png -------------------------------------------------------------------------------- /Readme/Picture7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Readme/Picture7.png -------------------------------------------------------------------------------- /Readme/Picture8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Readme/Picture8.png -------------------------------------------------------------------------------- /Readme/Picture9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/Readme/Picture9.png -------------------------------------------------------------------------------- /TestingMovieLib/AIKA/DASS-377-fanart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/AIKA/DASS-377-fanart.jpg -------------------------------------------------------------------------------- /TestingMovieLib/AIKA/DASS-377-poster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/AIKA/DASS-377-poster.jpg -------------------------------------------------------------------------------- /TestingMovieLib/AIKA/DASS-377-thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/AIKA/DASS-377-thumb.jpg -------------------------------------------------------------------------------- /TestingMovieLib/AIKA/DASS-377.mp4: -------------------------------------------------------------------------------- 1 |  2 | 3 | DASS-377 美人母娘 イタダキマス。数十年前に孕ませた女とその娘に会いに来ました。 AIKA 白石もも 4 | DASS-377 美人母娘 イタダキマス。数十年前に孕ませた女とその娘に会いに来ました。 AIKA 白石もも 5 | DASS-377 美人母娘 イタダキマス。数十年前に孕ませた女とその娘に会いに来ました。 AIKA 白石もも 6 | JP-18+ 7 | JP-18+ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | poster.jpg 21 | thumb.jpg 22 | fanart.jpg 23 | 24 | AIKA 25 | 26 | 27 | 白石もも 28 | 29 | 30 | 31 | 33 | DASS-377 34 | AIKA 35 | 白石もも 36 | 母娘 37 | 母親 38 | 中出 39 | 口交 40 | 舔陰 41 | 近親相姦 42 | 接吻 43 | 女上位 44 | DASS-377 45 | AIKA 46 | 白石もも 47 | 母娘 48 | 母親 49 | 中出 50 | 口交 51 | 舔陰 52 | 近親相姦 53 | 接吻 54 | 女上位 55 | DASS-377 56 | 57 | 58 | 59 | 60 | 61 | 62 | https://javday.tv/upload/vod/20240408-1/10108124f43141df25e693bb6a27bd24.jpg 63 | https://javday.tv/videos/DASS377/ 64 | 65 | 畑中哲也 66 | Actor 67 | 68 | -------------------------------------------------------------------------------- /TestingMovieLib/AIKA/DASS-377.nfo: -------------------------------------------------------------------------------- 1 |  2 | 3 | DASS-377 美人母娘 イタダキマス。数十年前に孕ませた女とその娘に会いに来ました。 AIKA 白石もも 4 | DASS-377 美人母娘 イタダキマス。数十年前に孕ませた女とその娘に会いに来ました。 AIKA 白石もも 5 | DASS-377 美人母娘 イタダキマス。数十年前に孕ませた女とその娘に会いに来ました。 AIKA 白石もも 6 | JP-18+ 7 | JP-18+ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | poster.jpg 21 | thumb.jpg 22 | fanart.jpg 23 | 24 | AIKA 25 | 26 | 27 | 白石もも 28 | 29 | 30 | 31 | 33 | DASS-377 34 | AIKA 35 | 白石もも 36 | 母娘 37 | 母親 38 | 中出 39 | 口交 40 | 舔陰 41 | 近親相姦 42 | 接吻 43 | 女上位 44 | DASS-377 45 | AIKA 46 | 白石もも 47 | 母娘 48 | 母親 49 | 中出 50 | 口交 51 | 舔陰 52 | 近親相姦 53 | 接吻 54 | 女上位 55 | DASS-377 56 | 57 | 58 | 59 | 60 | 61 | 62 | https://javday.tv/upload/vod/20240408-1/10108124f43141df25e693bb6a27bd24.jpg 63 | https://javday.tv/videos/DASS377/ 64 | 65 | 畑中哲也 66 | Actor 67 | 68 | -------------------------------------------------------------------------------- /TestingMovieLib/愛世くらら/IPX-100-fanart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/愛世くらら/IPX-100-fanart.jpg -------------------------------------------------------------------------------- /TestingMovieLib/愛世くらら/IPX-100-poster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/愛世くらら/IPX-100-poster.jpg -------------------------------------------------------------------------------- /TestingMovieLib/愛世くらら/IPX-100-trailer.mp4: -------------------------------------------------------------------------------- 1 |  2 | 3 | 長ベロのドエロいお姉さんお姉さんと思う存分じっくりねっとりイヤラシイ接吻とセックスを…。くらら姉さんとおじさんのべちょべちょレロレロいやらし接吻ハーモニー。美女とおじさんのネバネバ濃密唾液交換。唇に乳首にチンポにネッチョリ絡みつく粘度の高い唾液。老いた肉体をヨダレをだらだら垂らし長い舌で隅々までベロベロ吸い舐め回し中年の渾身のピストンにヨダレ汁と本気汁をたらしイキまくる! 4 | 5 | NC-17 6 | false 7 | 2019-09-24 23:37:30 8 | IPX-100 ドエロいお姉さんと交わすヨダレだらだらツバだくだく濃厚な接吻とセックス 愛世くらら 9 | IPX-100 ドエロいお姉さんと交わすヨダレだらだらツバだくだく濃厚な接吻とセックス 愛世くらら 10 | うさぴょん。 11 | 2018 12 | NC-17 13 | IPX-100 14 | JP 15 | 2018-03-03 16 | 2018-03-03 17 | 158 18 | 日本 19 | 系列:濃厚な接吻とSEX 20 | 片商:アイデアポケット 21 | 单体作品 22 | DMM独家 23 | 荡妇 24 | 姐姐 25 | 数位马赛克 26 | 接吻. 27 | アイデアポケット 28 | 系列:濃厚な接吻とSEX 29 | 片商:アイデアポケット 30 | 单体作品 31 | DMM独家 32 | 荡妇 33 | 姐姐 34 | 数位马赛克 35 | 接吻. 36 | 37 | E:\MyFile\New\有码\演员\愛世くらら\IPX-100 ドエロいお姉さんと交わすヨダレだらだらツバだくだく濃厚な接吻とセックス 愛世くらら-poster.jpg 38 | E:\MyFile\New\有码\演员\愛世くらら\IPX-100 ドエロいお姉さんと交わすヨダレだらだらツバだくだく濃厚な接吻とセックス 愛世くらら-fanart.jpg 39 | 40 | 41 | 愛世くらら 42 | Actor 43 | 44 | 45 | ウサミ 46 | Actor 47 | 48 | 49 | 吉村卓 50 | Actor 51 | C:\ProgramData\Jellyfin\Server\metadata\People\吉\吉村卓\folder.jpg 52 | 53 | 54 | 田淵正浩 55 | Actor 56 | C:\ProgramData\Jellyfin\Server\metadata\People\田\田淵正浩\folder.jpg 57 | 58 | IPX-100 59 | 濃厚な接吻とSEX 60 | 61 | 62 | 78 | 89 | 90 | 91 | 2018-03-03 92 | IPX-100 93 | -------------------------------------------------------------------------------- /TestingMovieLib/愛世くらら/IPX-100.mp4: -------------------------------------------------------------------------------- 1 |  2 | 3 | 長ベロのドエロいお姉さんお姉さんと思う存分じっくりねっとりイヤラシイ接吻とセックスを…。くらら姉さんとおじさんのべちょべちょレロレロいやらし接吻ハーモニー。美女とおじさんのネバネバ濃密唾液交換。唇に乳首にチンポにネッチョリ絡みつく粘度の高い唾液。老いた肉体をヨダレをだらだら垂らし長い舌で隅々までベロベロ吸い舐め回し中年の渾身のピストンにヨダレ汁と本気汁をたらしイキまくる! 4 | 5 | NC-17 6 | false 7 | 2019-09-24 23:37:30 8 | IPX-100 ドエロいお姉さんと交わすヨダレだらだらツバだくだく濃厚な接吻とセックス 愛世くらら 9 | IPX-100 ドエロいお姉さんと交わすヨダレだらだらツバだくだく濃厚な接吻とセックス 愛世くらら 10 | うさぴょん。 11 | 2018 12 | NC-17 13 | IPX-100 14 | JP 15 | 2018-03-03 16 | 2018-03-03 17 | 158 18 | 日本 19 | 系列:濃厚な接吻とSEX 20 | 片商:アイデアポケット 21 | 单体作品 22 | DMM独家 23 | 荡妇 24 | 姐姐 25 | 数位马赛克 26 | 接吻. 27 | アイデアポケット 28 | 系列:濃厚な接吻とSEX 29 | 片商:アイデアポケット 30 | 单体作品 31 | DMM独家 32 | 荡妇 33 | 姐姐 34 | 数位马赛克 35 | 接吻. 36 | 37 | E:\MyFile\New\有码\演员\愛世くらら\IPX-100 ドエロいお姉さんと交わすヨダレだらだらツバだくだく濃厚な接吻とセックス 愛世くらら-poster.jpg 38 | E:\MyFile\New\有码\演员\愛世くらら\IPX-100 ドエロいお姉さんと交わすヨダレだらだらツバだくだく濃厚な接吻とセックス 愛世くらら-fanart.jpg 39 | 40 | 41 | 愛世くらら 42 | Actor 43 | 44 | 45 | ウサミ 46 | Actor 47 | 48 | 49 | 吉村卓 50 | Actor 51 | C:\ProgramData\Jellyfin\Server\metadata\People\吉\吉村卓\folder.jpg 52 | 53 | 54 | 田淵正浩 55 | Actor 56 | C:\ProgramData\Jellyfin\Server\metadata\People\田\田淵正浩\folder.jpg 57 | 58 | IPX-100 59 | 濃厚な接吻とSEX 60 | 61 | 62 | 78 | 89 | 90 | 91 | 2018-03-03 92 | IPX-100 93 | -------------------------------------------------------------------------------- /TestingMovieLib/愛世くらら/IPX-100.nfo: -------------------------------------------------------------------------------- 1 |  2 | 3 | 長ベロのドエロいお姉さんお姉さんと思う存分じっくりねっとりイヤラシイ接吻とセックスを…。くらら姉さんとおじさんのべちょべちょレロレロいやらし接吻ハーモニー。美女とおじさんのネバネバ濃密唾液交換。唇に乳首にチンポにネッチョリ絡みつく粘度の高い唾液。老いた肉体をヨダレをだらだら垂らし長い舌で隅々までベロベロ吸い舐め回し中年の渾身のピストンにヨダレ汁と本気汁をたらしイキまくる! 4 | 5 | NC-17 6 | false 7 | 2019-09-24 23:37:30 8 | IPX-100 ドエロいお姉さんと交わすヨダレだらだらツバだくだく濃厚な接吻とセックス 愛世くらら 9 | IPX-100 ドエロいお姉さんと交わすヨダレだらだらツバだくだく濃厚な接吻とセックス 愛世くらら 10 | うさぴょん。 11 | 2018 12 | NC-17 13 | IPX-100 14 | JP 15 | 2018-03-03 16 | 2018-03-03 17 | 158 18 | 日本 19 | 系列:濃厚な接吻とSEX 20 | 片商:アイデアポケット 21 | 单体作品 22 | DMM独家 23 | 荡妇 24 | 姐姐 25 | 数位马赛克 26 | 接吻. 27 | アイデアポケット 28 | 系列:濃厚な接吻とSEX 29 | 片商:アイデアポケット 30 | 单体作品 31 | DMM独家 32 | 荡妇 33 | 姐姐 34 | 数位马赛克 35 | 接吻. 36 | 37 | E:\MyFile\New\有码\演员\愛世くらら\IPX-100 ドエロいお姉さんと交わすヨダレだらだらツバだくだく濃厚な接吻とセックス 愛世くらら-poster.jpg 38 | E:\MyFile\New\有码\演员\愛世くらら\IPX-100 ドエロいお姉さんと交わすヨダレだらだらツバだくだく濃厚な接吻とセックス 愛世くらら-fanart.jpg 39 | 40 | 41 | 愛世くらら 42 | Actor 43 | 44 | 45 | ウサミ 46 | Actor 47 | 48 | 49 | 吉村卓 50 | Actor 51 | C:\ProgramData\Jellyfin\Server\metadata\People\吉\吉村卓\folder.jpg 52 | 53 | 54 | 田淵正浩 55 | Actor 56 | C:\ProgramData\Jellyfin\Server\metadata\People\田\田淵正浩\folder.jpg 57 | 58 | IPX-100 59 | 濃厚な接吻とSEX 60 | 61 | 62 | 78 | 89 | 90 | 91 | 2018-03-03 92 | IPX-100 93 | -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/MIDV-679 彼女の双子のお姉さんが肉食すぎるアプローチで僕を誘惑 滝冬ひかり .mp4: -------------------------------------------------------------------------------- 1 |  2 | 3 | MIDV-679 彼女の双子のお姉さんが肉食すぎるアプローチで僕を誘惑 滝冬ひかり 4 | MIDV-679 彼女の双子のお姉さんが肉食すぎるアプローチで僕を誘惑 滝冬ひかり 5 | MIDV-679 彼女の双子のお姉さんが肉食すぎるアプローチで僕を誘惑 滝冬ひかり 6 | JP-18+ 7 | JP-18+ 8 | 9 | 10 | MOODYZ 11 | 2024 12 | 13 | 14 | 120 15 | キョウセイ 16 | poster.jpg 17 | thumb.jpg 18 | fanart.jpg 19 | 20 | 滝冬ひかり 21 | https://c0.jdbstatic.com/avatars/8v/8VODV.jpg 22 | 23 | 24 | 鈴音まゆ 25 | https://c0.jdbstatic.com/avatars/9d/9DxJ6.jpg 26 | 27 | MOODYZ 28 | 30 | 中文字幕 31 | 蕩婦 32 | 白天出軌 33 | 苗條 34 | 妹妹 35 | 接吻 36 | 中文字幕 37 | 蕩婦 38 | 白天出軌 39 | 苗條 40 | 妹妹 41 | 接吻 42 | MIDV-679 43 | 2024-04-16 44 | 2024-04-16 45 | 2024-04-16 46 | 7.9 47 | 78.8 48 | 49 | 50 | 3.94 51 | 518 52 | 53 | 54 | https://javday.tv/upload/vod/20240412-1/bf06a6cde7c6d1d74fa6d10880150659.jpg 55 | https://javdb.com/v/NQwPEN 56 | 57 | 結城結弦 58 | Actor 59 | 60 | -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/MIDV-679 彼女の双子のお姉さんが肉食すぎるアプローチで僕を誘惑 滝冬ひかり.nfo: -------------------------------------------------------------------------------- 1 |  2 | 3 | MIDV-679 彼女の双子のお姉さんが肉食すぎるアプローチで僕を誘惑 滝冬ひかり 4 | MIDV-679 彼女の双子のお姉さんが肉食すぎるアプローチで僕を誘惑 滝冬ひかり 5 | MIDV-679 彼女の双子のお姉さんが肉食すぎるアプローチで僕を誘惑 滝冬ひかり 6 | JP-18+ 7 | JP-18+ 8 | 9 | 10 | MOODYZ 11 | 2024 12 | 13 | 14 | 120 15 | キョウセイ 16 | poster.jpg 17 | thumb.jpg 18 | fanart.jpg 19 | 20 | 滝冬ひかり 21 | https://c0.jdbstatic.com/avatars/8v/8VODV.jpg 22 | 23 | 24 | 鈴音まゆ 25 | https://c0.jdbstatic.com/avatars/9d/9DxJ6.jpg 26 | 27 | MOODYZ 28 | 30 | 中文字幕 31 | 蕩婦 32 | 白天出軌 33 | 苗條 34 | 妹妹 35 | 接吻 36 | 中文字幕 37 | 蕩婦 38 | 白天出軌 39 | 苗條 40 | 妹妹 41 | 接吻 42 | MIDV-679 43 | 2024-04-16 44 | 2024-04-16 45 | 2024-04-16 46 | 7.9 47 | 78.8 48 | 49 | 50 | 3.94 51 | 518 52 | 53 | 54 | https://javday.tv/upload/vod/20240412-1/bf06a6cde7c6d1d74fa6d10880150659.jpg 55 | https://javdb.com/v/NQwPEN 56 | 57 | 結城結弦 58 | Actor 59 | 60 | -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-1.jpg -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-10.jpg -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-11.jpg -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-12.jpg -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-13.jpg -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-2.jpg -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-3.jpg -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-4.jpg -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-5.jpg -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-6.jpg -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-7.jpg -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-8.jpg -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/extrafanart/extrafanart-9.jpg -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/fanart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/fanart.jpg -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/poster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/poster.jpg -------------------------------------------------------------------------------- /TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4evergaeul/JAV_MovieManager/ff354f1042599775ccfa2521aa18b4162ec102ec/TestingMovieLib/滝冬ひかり,鈴音まゆ/MIDV-679/thumb.jpg --------------------------------------------------------------------------------