diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 9711862..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index c5b6d2e..d97ed97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,405 +1,405 @@ -# globs -Makefile.in -*.userprefs -*.usertasks -config.make -config.status -aclocal.m4 -install-sh -autom4te.cache/ -*.tar.gz -tarballs/ -test-results/ - -# Mac bundle stuff -*.dmg -*.app - -# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore -# Windows thumbnail cache files -Thumbs.db -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# content below from: https://github.com/github/gitignore/blob/master/VisualStudio.gitignore -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ \ No newline at end of file +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# content below from: https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ diff --git a/README.MD b/README.MD index 0a48c1f..d341539 100644 --- a/README.MD +++ b/README.MD @@ -5,6 +5,7 @@ This is the server for the Gaseous system. All your games and metadata are store ## Screenshots ![Library](./screenshots/Library.png) ![Game](./screenshots/Game.png) +![Emulator](./screenshots/Emulator.png) ## Requirements * MySQL Server 8+ @@ -18,6 +19,9 @@ The following projects are used by Gaseous * https://github.com/kamranayub/igdb-dotnet * https://github.com/EmulatorJS/EmulatorJS +## Discord Server +[![Join our Discord server!](https://invite.casperiv.dev/?inviteCode=Nhu7wpT3k4&format=svg)](https://discord.gg/Nhu7wpT3k4) + ## Configuration File When Gaseous-Server is started for the first time, it creates a configuration file at ~/.gaseous-server/config.json if it doesn't exist. Some values can be filled in using environment variables (such as in the case of using docker). diff --git a/gaseous-server/Assets/Ratings/.DS_Store b/gaseous-server/Assets/Ratings/.DS_Store index fad90d5..47d5f24 100644 Binary files a/gaseous-server/Assets/Ratings/.DS_Store and b/gaseous-server/Assets/Ratings/.DS_Store differ diff --git a/gaseous-server/Classes/Collections.cs b/gaseous-server/Classes/Collections.cs new file mode 100644 index 0000000..1cf58b1 --- /dev/null +++ b/gaseous-server/Classes/Collections.cs @@ -0,0 +1,607 @@ +using System; +using System.Data; +using System.IO.Compression; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using gaseous_server.Classes.Metadata; +using gaseous_server.Controllers; +using gaseous_tools; +using IGDB.Models; +using Newtonsoft.Json; + +namespace gaseous_server.Classes +{ + public class Collections + { + public Collections() + { + + } + + public static List GetCollections() { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM RomCollections ORDER BY `Name`"; + + DataTable data = db.ExecuteCMD(sql); + + List collectionItems = new List(); + + foreach(DataRow row in data.Rows) { + collectionItems.Add(BuildCollectionItem(row)); + } + + return collectionItems; + } + + public static CollectionItem GetCollection(long Id) { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM RomCollections WHERE Id = @id ORDER BY `Name`"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", Id); + DataTable romDT = db.ExecuteCMD(sql, dbDict); + + if (romDT.Rows.Count > 0) + { + DataRow row = romDT.Rows[0]; + CollectionItem collectionItem = BuildCollectionItem(row); + + return collectionItem; + } + else + { + throw new Exception("Unknown Collection Id"); + } + } + + public static CollectionItem NewCollection(CollectionItem item) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "INSERT INTO RomCollections (`Name`, Description, Platforms, Genres, Players, PlayerPerspectives, Themes, MinimumRating, MaximumRating, MaximumRomsPerPlatform, MaximumBytesPerPlatform, MaximumCollectionSizeInBytes, BuiltStatus) VALUES (@name, @description, @platforms, @genres, @players, @playerperspectives, @themes, @minimumrating, @maximumrating, @maximumromsperplatform, @maximumbytesperplatform, @maximumcollectionsizeinbytes, @builtstatus); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("name", item.Name); + dbDict.Add("description", item.Description); + dbDict.Add("platforms", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Platforms, new List()))); + dbDict.Add("genres", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Genres, new List()))); + dbDict.Add("players", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Players, new List()))); + dbDict.Add("playerperspectives", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.PlayerPerspectives, new List()))); + dbDict.Add("themes", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Themes, new List()))); + dbDict.Add("minimumrating", Common.ReturnValueIfNull(item.MinimumRating, -1)); + dbDict.Add("maximumrating", Common.ReturnValueIfNull(item.MaximumRating, -1)); + dbDict.Add("maximumromsperplatform", Common.ReturnValueIfNull(item.MaximumRomsPerPlatform, -1)); + dbDict.Add("maximumbytesperplatform", Common.ReturnValueIfNull(item.MaximumBytesPerPlatform, -1)); + dbDict.Add("maximumcollectionsizeinbytes", Common.ReturnValueIfNull(item.MaximumCollectionSizeInBytes, -1)); + dbDict.Add("builtstatus", CollectionItem.CollectionBuildStatus.WaitingForBuild); + DataTable romDT = db.ExecuteCMD(sql, dbDict); + long CollectionId = (long)romDT.Rows[0][0]; + + CollectionItem collectionItem = GetCollection(CollectionId); + + StartCollectionItemBuild(CollectionId); + + return collectionItem; + } + + public static CollectionItem EditCollection(long Id, CollectionItem item) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "UPDATE RomCollections SET `Name`=@name, Description=@description, Platforms=@platforms, Genres=@genres, Players=@players, PlayerPerspectives=@playerperspectives, Themes=@themes, MinimumRating=@minimumrating, MaximumRating=@maximumrating, MaximumRomsPerPlatform=@maximumromsperplatform, MaximumBytesPerPlatform=@maximumbytesperplatform, MaximumCollectionSizeInBytes=@maximumcollectionsizeinbytes, BuiltStatus=@builtstatus WHERE Id=@id"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", Id); + dbDict.Add("name", item.Name); + dbDict.Add("description", item.Description); + dbDict.Add("platforms", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Platforms, new List()))); + dbDict.Add("genres", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Genres, new List()))); + dbDict.Add("players", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Players, new List()))); + dbDict.Add("playerperspectives", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.PlayerPerspectives, new List()))); + dbDict.Add("themes", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Themes, new List()))); + dbDict.Add("minimumrating", Common.ReturnValueIfNull(item.MinimumRating, -1)); + dbDict.Add("maximumrating", Common.ReturnValueIfNull(item.MaximumRating, -1)); + dbDict.Add("maximumromsperplatform", Common.ReturnValueIfNull(item.MaximumRomsPerPlatform, -1)); + dbDict.Add("maximumbytesperplatform", Common.ReturnValueIfNull(item.MaximumBytesPerPlatform, -1)); + dbDict.Add("maximumcollectionsizeinbytes", Common.ReturnValueIfNull(item.MaximumCollectionSizeInBytes, -1)); + dbDict.Add("builtstatus", CollectionItem.CollectionBuildStatus.WaitingForBuild); + db.ExecuteCMD(sql, dbDict); + + string CollectionZipFile = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + ".zip"); + if (File.Exists(CollectionZipFile)) + { + File.Delete(CollectionZipFile); + } + + CollectionItem collectionItem = GetCollection(Id); + + StartCollectionItemBuild(Id); + + return collectionItem; + } + + public static void DeleteCollection(long Id) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "DELETE FROM RomCollections WHERE Id=@id"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", Id); + db.ExecuteCMD(sql, dbDict); + + string CollectionZipFile = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + ".zip"); + if (File.Exists(CollectionZipFile)) + { + File.Delete(CollectionZipFile); + } + } + + public static void StartCollectionItemBuild(long Id) + { + CollectionItem collectionItem = GetCollection(Id); + + if (collectionItem.BuildStatus != CollectionItem.CollectionBuildStatus.Building) + { + // set collection item to waitingforbuild + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "UPDATE RomCollections SET BuiltStatus=@bs WHERE Id=@id"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", Id); + dbDict.Add("bs", CollectionItem.CollectionBuildStatus.WaitingForBuild); + db.ExecuteCMD(sql, dbDict); + + // start background task + foreach (ProcessQueue.QueueItem qi in ProcessQueue.QueueItems) + { + if (qi.ItemType == ProcessQueue.QueueItemType.CollectionCompiler) { + qi.ForceExecute(); + break; + } + } + } + } + + public static CollectionContents GetCollectionContent(CollectionItem collectionItem) { + List collectionPlatformItems = new List(); + + // get platforms + List platforms = new List(); + if (collectionItem.Platforms.Count > 0) { + foreach (long PlatformId in collectionItem.Platforms) { + platforms.Add(Platforms.GetPlatform(PlatformId)); + } + } else { + // get all platforms to pull from + FilterController filterController = new FilterController(); + platforms.AddRange((List)filterController.Filter()["platforms"]); + } + + // build collection + List platformItems = new List(); + + foreach (Platform platform in platforms) { + long TotalRomSize = 0; + long TotalGameCount = 0; + + List games = GamesController.GetGames("", + platform.Id.ToString(), + string.Join(",", collectionItem.Genres), + string.Join(",", collectionItem.Players), + string.Join(",", collectionItem.PlayerPerspectives), + string.Join(",", collectionItem.Themes), + collectionItem.MinimumRating, + collectionItem.MaximumRating + ); + + CollectionContents.CollectionPlatformItem collectionPlatformItem = new CollectionContents.CollectionPlatformItem(platform); + collectionPlatformItem.Games = new List(); + + foreach (Game game in games) { + CollectionContents.CollectionPlatformItem.CollectionGameItem collectionGameItem = new CollectionContents.CollectionPlatformItem.CollectionGameItem(game); + + List gameRoms = Roms.GetRoms((long)game.Id, (long)platform.Id); + + bool AddGame = false; + + // calculate total rom size for the game + long GameRomSize = 0; + foreach (Roms.GameRomItem gameRom in gameRoms) { + GameRomSize += gameRom.Size; + } + if (collectionItem.MaximumBytesPerPlatform > 0) { + if ((TotalRomSize + GameRomSize) < collectionItem.MaximumBytesPerPlatform) { + AddGame = true; + } + } + else + { + AddGame = true; + } + + if (AddGame == true) { + TotalRomSize += GameRomSize; + + bool AddRoms = false; + + if (collectionItem.MaximumRomsPerPlatform > 0) { + if (TotalGameCount < collectionItem.MaximumRomsPerPlatform) { + AddRoms = true; + } + } + else + { + AddRoms = true; + } + + if (AddRoms == true) { + TotalGameCount += 1; + collectionGameItem.Roms = gameRoms; + collectionPlatformItem.Games.Add(collectionGameItem); + } + } + } + + if (collectionPlatformItem.Games.Count > 0) + { + bool AddPlatform = false; + if (collectionItem.MaximumCollectionSizeInBytes > 0) + { + if (TotalRomSize < collectionItem.MaximumCollectionSizeInBytes) + { + AddPlatform = true; + } + } + else + { + AddPlatform = true; + } + + if (AddPlatform == true) + { + collectionPlatformItems.Add(collectionPlatformItem); + } + } + } + + CollectionContents collectionContents = new CollectionContents(); + collectionContents.Collection = collectionPlatformItems; + return collectionContents; + } + + public static void CompileCollections() + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + + List collectionItems = GetCollections(); + foreach (CollectionItem collectionItem in collectionItems) + { + if (collectionItem.BuildStatus == CollectionItem.CollectionBuildStatus.WaitingForBuild) + { + // set starting + string sql = "UPDATE RomCollections SET BuiltStatus=@bs WHERE Id=@id"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", collectionItem.Id); + dbDict.Add("bs", CollectionItem.CollectionBuildStatus.Building); + db.ExecuteCMD(sql, dbDict); + + List collectionPlatformItems = GetCollectionContent(collectionItem).Collection; + string ZipFilePath = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, collectionItem.Id + ".zip"); + string ZipFileTempPath = Path.Combine(Config.LibraryConfiguration.LibraryTempDirectory, collectionItem.Id.ToString()); + + try + { + + // clean up if needed + if (File.Exists(ZipFilePath)) + { + File.Delete(ZipFilePath); + } + + if (Directory.Exists(ZipFileTempPath)) + { + Directory.Delete(ZipFileTempPath, true); + } + + // gather collection files + Directory.CreateDirectory(ZipFileTempPath); + + foreach (CollectionContents.CollectionPlatformItem collectionPlatformItem in collectionPlatformItems) + { + // create platform directory + string ZipPlatformPath = Path.Combine(ZipFileTempPath, collectionPlatformItem.Slug); + if (!Directory.Exists(ZipPlatformPath)) + { + Directory.CreateDirectory(ZipPlatformPath); + } + + foreach (CollectionContents.CollectionPlatformItem.CollectionGameItem collectionGameItem in collectionPlatformItem.Games) + { + // create game directory + string ZipGamePath = Path.Combine(ZipPlatformPath, collectionGameItem.Slug); + if (!Directory.Exists(ZipGamePath)) + { + Directory.CreateDirectory(ZipGamePath); + } + + // copy in roms + foreach (Roms.GameRomItem gameRomItem in collectionGameItem.Roms) + { + if (File.Exists(gameRomItem.Path)) + { + File.Copy(gameRomItem.Path, Path.Combine(ZipGamePath, gameRomItem.Name)); + } + } + } + } + + // compress to zip + ZipFile.CreateFromDirectory(ZipFileTempPath, ZipFilePath, CompressionLevel.SmallestSize, false); + + // clean up + if (Directory.Exists(ZipFileTempPath)) + { + Directory.Delete(ZipFileTempPath, true); + } + + // set completed + dbDict["bs"] = CollectionItem.CollectionBuildStatus.Completed; + db.ExecuteCMD(sql, dbDict); + } + catch (Exception ex) + { + // clean up + if (Directory.Exists(ZipFileTempPath)) + { + Directory.Delete(ZipFileTempPath, true); + } + + if (File.Exists(ZipFilePath)) + { + File.Delete(ZipFilePath); + } + + // set failed + dbDict["bs"] = CollectionItem.CollectionBuildStatus.Failed; + db.ExecuteCMD(sql, dbDict); + + Logging.Log(Logging.LogType.Critical, "Collection Builder", "Collection building has failed", ex); + } + } + } + } + + private static CollectionItem BuildCollectionItem(DataRow row) { + string strPlatforms = (string)Common.ReturnValueIfNull(row["Platforms"], "[ ]"); + string strGenres = (string)Common.ReturnValueIfNull(row["Genres"], "[ ]"); + string strPlayers = (string)Common.ReturnValueIfNull(row["Players"], "[ ]"); + string strPlayerPerspectives = (string)Common.ReturnValueIfNull(row["PlayerPerspectives"], "[ ]"); + string strThemes = (string)Common.ReturnValueIfNull(row["Themes"], "[ ]"); + + CollectionItem item = new CollectionItem(); + item.Id = (long)row["Id"]; + item.Name = (string)row["Name"]; + item.Description = (string)row["Description"]; + item.Platforms = Newtonsoft.Json.JsonConvert.DeserializeObject>(strPlatforms); + item.Genres = Newtonsoft.Json.JsonConvert.DeserializeObject>(strGenres); + item.Players = Newtonsoft.Json.JsonConvert.DeserializeObject>(strPlayers); + item.PlayerPerspectives = Newtonsoft.Json.JsonConvert.DeserializeObject>(strPlayerPerspectives); + item.Themes = Newtonsoft.Json.JsonConvert.DeserializeObject>(strThemes); + item.MinimumRating = (int)Common.ReturnValueIfNull(row["MinimumRating"], -1); + item.MaximumRating = (int)Common.ReturnValueIfNull(row["MaximumRating"], -1); + item.MaximumRomsPerPlatform = (int)Common.ReturnValueIfNull(row["MaximumRomsPerPlatform"], (int)-1); + item.MaximumBytesPerPlatform = (long)Common.ReturnValueIfNull(row["MaximumBytesPerPlatform"], (long)-1); + item.MaximumCollectionSizeInBytes = (long)Common.ReturnValueIfNull(row["MaximumCollectionSizeInBytes"], (long)-1); + item.BuildStatus = (CollectionItem.CollectionBuildStatus)(int)Common.ReturnValueIfNull(row["BuiltStatus"], 0); + + return item; + } + + public class CollectionItem + { + public CollectionItem() + { + + } + + public long Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public List? Platforms { get; set; } + public List? Genres { get; set; } + public List? Players { get; set; } + public List? PlayerPerspectives { get; set; } + public List? Themes { get; set; } + public int MinimumRating { get; set; } + public int MaximumRating { get; set; } + public int? MaximumRomsPerPlatform { get; set; } + public long? MaximumBytesPerPlatform { get; set; } + public long? MaximumCollectionSizeInBytes { get; set; } + + [JsonIgnore] + public CollectionBuildStatus BuildStatus + { + get + { + if (_BuildStatus == CollectionBuildStatus.Completed) + { + if (File.Exists(Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + ".zip"))) + { + return CollectionBuildStatus.Completed; + } + else + { + return CollectionBuildStatus.NoStatus; + } + } + else + { + return _BuildStatus; + } + } + set + { + _BuildStatus = value; + } + } + private CollectionBuildStatus _BuildStatus { get; set; } + + [JsonIgnore] + public long CollectionBuiltSizeBytes + { + get + { + if (BuildStatus == CollectionBuildStatus.Completed) + { + string ZipFilePath = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + ".zip"); + if (File.Exists(ZipFilePath)) + { + FileInfo fi = new FileInfo(ZipFilePath); + return fi.Length; + } + else + { + return 0; + } + } + else + { + return 0; + } + } + } + + public enum CollectionBuildStatus + { + NoStatus = 0, + WaitingForBuild = 1, + Building = 2, + Completed = 3, + Failed = 4 + } + } + + public class CollectionContents { + [JsonIgnore] + public List Collection { get; set; } + + [JsonIgnore] + public long CollectionProjectedSizeBytes + { + get + { + long CollectionSize = 0; + + List collectionPlatformItems = new List(); + + if (Collection != null) + { + collectionPlatformItems = Collection; + } + + foreach (CollectionPlatformItem platformItem in collectionPlatformItems) + { + CollectionSize += platformItem.RomSize; + } + + return CollectionSize; + } + } + + public class CollectionPlatformItem { + public CollectionPlatformItem(IGDB.Models.Platform platform) { + string[] PropertyWhitelist = new string[] { "Id", "Name", "Slug" }; + + PropertyInfo[] srcProperties = typeof(IGDB.Models.Platform).GetProperties(); + PropertyInfo[] dstProperties = typeof(CollectionPlatformItem).GetProperties(); + foreach (PropertyInfo srcProperty in srcProperties) { + if (PropertyWhitelist.Contains(srcProperty.Name)) + { + foreach (PropertyInfo dstProperty in dstProperties) + { + if (srcProperty.Name == dstProperty.Name) + { + dstProperty.SetValue(this, srcProperty.GetValue(platform)); + } + } + } + } + } + + public long Id { get; set; } + public string Name { get; set; } + public string Slug { get; set; } + + public List Games { get; set; } + + public int RomCount { + get { + int Counter = 0; + foreach (CollectionGameItem Game in Games) { + Counter += 1; + } + + return Counter; + } + } + + public long RomSize { + get { + long Size = 0; + foreach (CollectionGameItem Game in Games) { + foreach (Roms.GameRomItem Rom in Game.Roms) { + Size += Rom.Size; + } + } + + return Size; + } + } + + public class CollectionGameItem { + public CollectionGameItem(IGDB.Models.Game game) { + string[] PropertyWhitelist = new string[] { "Id", "Name", "Slug", "Cover" }; + PropertyInfo[] srcProperties = typeof(IGDB.Models.Game).GetProperties(); + PropertyInfo[] dstProperties = typeof(CollectionPlatformItem.CollectionGameItem).GetProperties(); + foreach (PropertyInfo srcProperty in srcProperties) { + if (PropertyWhitelist.Contains(srcProperty.Name)) + { + foreach (PropertyInfo dstProperty in dstProperties) + { + if (srcProperty.Name == dstProperty.Name) + { + if (srcProperty.GetValue(game) != null) { + string compareName = srcProperty.PropertyType.Name.ToLower().Split("`")[0]; + switch(compareName) { + case "identityorvalue": + string newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(srcProperty.GetValue(game)); + Dictionary newDict = Newtonsoft.Json.JsonConvert.DeserializeObject>(newObjectValue); + dstProperty.SetValue(this, newDict["Id"]); + break; + default: + dstProperty.SetValue(this, srcProperty.GetValue(game)); + break; + } + } + } + } + } + } + } + + public long Id { get; set; } + public string Name { get; set; } + public string Slug { get; set; } + public long Cover { get; set;} + + public List Roms { get; set; } + + public long RomSize { + get { + long Size = 0; + foreach (Roms.GameRomItem Rom in Roms) { + Size += Rom.Size; + } + + return Size; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/gaseous-server/Classes/ImportGames.cs b/gaseous-server/Classes/ImportGames.cs index 87009cb..5eae101 100644 --- a/gaseous-server/Classes/ImportGames.cs +++ b/gaseous-server/Classes/ImportGames.cs @@ -129,7 +129,7 @@ namespace gaseous_server.Classes { // file is a zip and less than 1 GiB // extract the zip file and search the contents - string ExtractPath = Path.Combine(Config.LibraryConfiguration.LibraryRootDirectory, "Temp", Path.GetRandomFileName()); + string ExtractPath = Path.Combine(Config.LibraryConfiguration.LibraryTempDirectory, Path.GetRandomFileName()); if (!Directory.Exists(ExtractPath)) { Directory.CreateDirectory(ExtractPath); } ZipFile.ExtractToDirectory(GameFileImportPath, ExtractPath); diff --git a/gaseous-server/Classes/Roms.cs b/gaseous-server/Classes/Roms.cs index 35b5f89..89dc647 100644 --- a/gaseous-server/Classes/Roms.cs +++ b/gaseous-server/Classes/Roms.cs @@ -6,12 +6,19 @@ namespace gaseous_server.Classes { public class Roms { - public static List GetRoms(long GameId) + public static List GetRoms(long GameId, long PlatformId = -1) { Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "SELECT * FROM Games_Roms WHERE GameId = @id ORDER BY `Name`"; - Dictionary dbDict = new Dictionary(); + string sql = ""; + Dictionary dbDict = new Dictionary(); dbDict.Add("id", GameId); + + if (PlatformId == -1) { + sql = "SELECT * FROM Games_Roms WHERE GameId = @id ORDER BY `Name`"; + } else { + sql = "SELECT * FROM Games_Roms WHERE GameId = @id AND PlatformId = @platformid ORDER BY `Name`"; + dbDict.Add("platformid", PlatformId); + } DataTable romDT = db.ExecuteCMD(sql, dbDict); if (romDT.Rows.Count > 0) diff --git a/gaseous-server/Controllers/BackgroundTasksController.cs b/gaseous-server/Controllers/BackgroundTasksController.cs index 062ffad..ce94d07 100644 --- a/gaseous-server/Controllers/BackgroundTasksController.cs +++ b/gaseous-server/Controllers/BackgroundTasksController.cs @@ -25,13 +25,16 @@ namespace gaseous_server.Controllers { foreach (ProcessQueue.QueueItem qi in ProcessQueue.QueueItems) { - if (TaskType == qi.ItemType) - { - if (ForceRun == true) + if (qi.AllowManualStart == true) + { + if (TaskType == qi.ItemType) { - qi.ForceExecute(); + if (ForceRun == true) + { + qi.ForceExecute(); + } + return qi; } - return qi; } } diff --git a/gaseous-server/Controllers/CollectionsController.cs b/gaseous-server/Controllers/CollectionsController.cs new file mode 100644 index 0000000..a8917da --- /dev/null +++ b/gaseous-server/Controllers/CollectionsController.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.IO.Compression; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace gaseous_server.Controllers +{ + [ApiController] + [Route("api/v1/[controller]")] + public class CollectionsController : Controller + { + /// + /// Gets all ROM collections + /// + /// + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public List GetCollections() + { + return Classes.Collections.GetCollections(); + } + + /// + /// Gets a specific ROM collection + /// + /// + /// Set to true to begin the collection build process + /// + [HttpGet] + [Route("{CollectionId}")] + [ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GetCollection(long CollectionId, bool Build = false) + { + try + { + if (Build == true) + { + Classes.Collections.StartCollectionItemBuild(CollectionId); + } + + return Ok(Classes.Collections.GetCollection(CollectionId)); + } + catch + { + return NotFound(); + } + } + + /// + /// Gets the contents of the specified ROM collection + /// + /// + /// + [HttpGet] + [Route("{CollectionId}/Roms")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GetCollectionRoms(long CollectionId) + { + try + { + Classes.Collections.CollectionItem collectionItem = Classes.Collections.GetCollection(CollectionId); + return Ok(Classes.Collections.GetCollectionContent(collectionItem)); + } + catch + { + return NotFound(); + } + } + + /// + /// Gets a preview of the provided collection item + /// + /// + /// + [HttpPost] + [Route("Preview")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GetCollectionRomsPreview(Classes.Collections.CollectionItem Item) + { + //try + //{ + return Ok(Classes.Collections.GetCollectionContent(Item)); + //} + //catch (Exception ex) + //{ + // return NotFound(ex); + //} + } + + /// + /// Gets ROM collection in zip format + /// + /// + /// + [HttpGet] + [Route("{CollectionId}/Roms/Zip")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GetCollectionRomsZip(long CollectionId) + { + try + { + Classes.Collections.CollectionItem collectionItem = Classes.Collections.GetCollection(CollectionId); + + string ZipFilePath = Path.Combine(gaseous_tools.Config.LibraryConfiguration.LibraryCollectionsDirectory, CollectionId + ".zip"); + + if (System.IO.File.Exists(ZipFilePath)) + { + var stream = new FileStream(ZipFilePath, FileMode.Open); + return File(stream, "application/zip", collectionItem.Name + ".zip"); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + /// + /// Creates a new ROM collection + /// + /// + /// + [HttpPost] + [ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public ActionResult NewCollection(Classes.Collections.CollectionItem Item) + { + try + { + return Ok(Classes.Collections.NewCollection(Item)); + } + catch (Exception ex) + { + return BadRequest(ex); + } + } + + /// + /// Edits an existing collection + /// + /// + /// + /// + [HttpPatch] + [Route("{CollectionId}")] + [ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult EditCollection(long CollectionId, Classes.Collections.CollectionItem Item) + { + try + { + return Ok(Classes.Collections.EditCollection(CollectionId, Item)); + } + catch + { + return NotFound(); + } + } + + /// + /// Deletes the specified ROM collection + /// + /// + [HttpDelete] + [Route("{CollectionId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult DeleteCollection(long CollectionId) + { + try + { + Classes.Collections.DeleteCollection(CollectionId); + return Ok(); + } + catch + { + return NotFound(); + } + } + } +} \ No newline at end of file diff --git a/gaseous-server/Controllers/GamesController.cs b/gaseous-server/Controllers/GamesController.cs index b035678..2a13402 100644 --- a/gaseous-server/Controllers/GamesController.cs +++ b/gaseous-server/Controllers/GamesController.cs @@ -1,28 +1,28 @@ -using System; +using System; using System.Collections; -using System.Collections.Generic; +using System.Collections.Generic; using System.Data; using System.IO; -using System.Linq; +using System.Linq; using System.Reflection; -using System.Threading.Tasks; +using System.Threading.Tasks; using gaseous_server.Classes.Metadata; using gaseous_tools; using IGDB.Models; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Microsoft.CodeAnalysis.Scripting; using Org.BouncyCastle.Asn1.X509; using static gaseous_server.Classes.Metadata.AgeRatings; -namespace gaseous_server.Controllers -{ - [Route("api/v1/[controller]")] - [ApiController] - public class GamesController : ControllerBase - { - [HttpGet] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] +namespace gaseous_server.Controllers +{ + [Route("api/v1/[controller]")] + [ApiController] + public class GamesController : ControllerBase + { + [HttpGet] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public ActionResult Game( string name = "", string platform = "", @@ -33,6 +33,21 @@ namespace gaseous_server.Controllers int minrating = -1, int maxrating = -1, bool sortdescending = false) + { + + return Ok(GetGames(name, platform, genre, gamemode, playerperspective, theme, minrating, maxrating, sortdescending)); + } + + public static List GetGames( + string name = "", + string platform = "", + string genre = "", + string gamemode = "", + string playerperspective = "", + string theme = "", + int minrating = -1, + int maxrating = -1, + bool sortdescending = false) { string whereClause = ""; string havingClause = ""; @@ -201,14 +216,14 @@ namespace gaseous_server.Controllers RetVal.Add(Classes.Metadata.Games.GetGame(dr)); } - return Ok(RetVal); - } - - [HttpGet] - [Route("{GameId}")] - [ProducesResponseType(typeof(Game), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ResponseCache(CacheProfileName = "5Minute")] + return RetVal; + } + + [HttpGet] + [Route("{GameId}")] + [ProducesResponseType(typeof(Game), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "5Minute")] public ActionResult Game(long GameId, bool forceRefresh = false) { try @@ -228,13 +243,13 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/alternativename")] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ResponseCache(CacheProfileName = "7Days")] + } + + [HttpGet] + [Route("{GameId}/alternativename")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] public ActionResult GameAlternativeNames(long GameId) { try @@ -259,13 +274,13 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/agerating")] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ResponseCache(CacheProfileName = "7Days")] + } + + [HttpGet] + [Route("{GameId}/agerating")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] public ActionResult GameAgeClassification(long GameId) { try @@ -290,12 +305,12 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/agerating/{RatingId}/image")] - [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + } + + [HttpGet] + [Route("{GameId}/agerating/{RatingId}/image")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GameAgeClassification(long GameId, long RatingId) { try @@ -369,13 +384,13 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/artwork")] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ResponseCache(CacheProfileName = "7Days")] + } + + [HttpGet] + [Route("{GameId}/artwork")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] public ActionResult GameArtwork(long GameId) { try @@ -398,13 +413,13 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/artwork/{ArtworkId}")] - [ProducesResponseType(typeof(Artwork), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ResponseCache(CacheProfileName = "7Days")] + } + + [HttpGet] + [Route("{GameId}/artwork/{ArtworkId}")] + [ProducesResponseType(typeof(Artwork), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] public ActionResult GameArtwork(long GameId, long ArtworkId) { try @@ -432,12 +447,12 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/artwork/{ArtworkId}/image")] - [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + } + + [HttpGet] + [Route("{GameId}/artwork/{ArtworkId}/image")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GameCoverImage(long GameId, long ArtworkId) { try @@ -486,13 +501,13 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/cover")] - [ProducesResponseType(typeof(Cover), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ResponseCache(CacheProfileName = "7Days")] + } + + [HttpGet] + [Route("{GameId}/cover")] + [ProducesResponseType(typeof(Cover), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] public ActionResult GameCover(long GameId) { try @@ -519,12 +534,12 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/cover/image")] - [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + } + + [HttpGet] + [Route("{GameId}/cover/image")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GameCoverImage(long GameId) { try @@ -558,13 +573,13 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/genre")] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ResponseCache(CacheProfileName = "7Days")] + } + + [HttpGet] + [Route("{GameId}/genre")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] public ActionResult GameGenre(long GameId) { try @@ -594,13 +609,13 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/companies")] - [ProducesResponseType(typeof(List>), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ResponseCache(CacheProfileName = "7Days")] + } + + [HttpGet] + [Route("{GameId}/companies")] + [ProducesResponseType(typeof(List>), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] public ActionResult GameInvolvedCompanies(long GameId) { try @@ -637,13 +652,13 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/companies/{CompanyId}")] - [ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ResponseCache(CacheProfileName = "7Days")] + } + + [HttpGet] + [Route("{GameId}/companies/{CompanyId}")] + [ProducesResponseType(typeof(Dictionary), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] public ActionResult GameInvolvedCompanies(long GameId, long CompanyId) { try @@ -678,12 +693,12 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/companies/{CompanyId}/image")] - [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + } + + [HttpGet] + [Route("{GameId}/companies/{CompanyId}/image")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GameCompanyImage(long GameId, long CompanyId) { try @@ -721,13 +736,13 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/roms")] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - //[ResponseCache(CacheProfileName = "5Minute")] + } + + [HttpGet] + [Route("{GameId}/roms")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + //[ResponseCache(CacheProfileName = "5Minute")] public ActionResult GameRom(long GameId) { try @@ -742,13 +757,13 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/roms/{RomId}")] - [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] + } + + [HttpGet] + [Route("{GameId}/roms/{RomId}")] + [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - //[ResponseCache(CacheProfileName = "5Minute")] + //[ResponseCache(CacheProfileName = "5Minute")] public ActionResult GameRom(long GameId, long RomId) { try @@ -769,12 +784,12 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpPatch] - [Route("{GameId}/roms/{RomId}")] - [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + } + + [HttpPatch] + [Route("{GameId}/roms/{RomId}")] + [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GameRomRename(long GameId, long RomId, long NewPlatformId, long NewGameId) { try @@ -796,12 +811,12 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpDelete] - [Route("{GameId}/roms/{RomId}")] - [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + } + + [HttpDelete] + [Route("{GameId}/roms/{RomId}")] + [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GameRomDelete(long GameId, long RomId) { try @@ -823,13 +838,13 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [HttpHead] - [Route("{GameId}/roms/{RomId}/file")] - [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + } + + [HttpGet] + [HttpHead] + [Route("{GameId}/roms/{RomId}/file")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GameRomFile(long GameId, long RomId) { try @@ -858,13 +873,13 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [HttpHead] - [Route("{GameId}/roms/{RomId}/{FileName}")] - [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + } + + [HttpGet] + [HttpHead] + [Route("{GameId}/roms/{RomId}/{FileName}")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GameRomFile(long GameId, long RomId, string FileName) { try @@ -893,12 +908,12 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("search")] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + } + + [HttpGet] + [Route("search")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GameSearch(long RomId = 0, string SearchString = "") { try @@ -930,13 +945,13 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/screenshots")] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ResponseCache(CacheProfileName = "7Days")] + } + + [HttpGet] + [Route("{GameId}/screenshots")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] public ActionResult GameScreenshot(long GameId) { try @@ -959,13 +974,13 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/screenshots/{ScreenshotId}")] - [ProducesResponseType(typeof(Screenshot), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ResponseCache(CacheProfileName = "7Days")] + } + + [HttpGet] + [Route("{GameId}/screenshots/{ScreenshotId}")] + [ProducesResponseType(typeof(Screenshot), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] public ActionResult GameScreenshot(long GameId, long ScreenshotId) { try @@ -991,12 +1006,12 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/screenshots/{ScreenshotId}/image")] - [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + } + + [HttpGet] + [Route("{GameId}/screenshots/{ScreenshotId}/image")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GameScreenshotImage(long GameId, long ScreenshotId) { try @@ -1033,13 +1048,13 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - - [HttpGet] - [Route("{GameId}/videos")] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ResponseCache(CacheProfileName = "7Days")] + } + + [HttpGet] + [Route("{GameId}/videos")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] public ActionResult GameVideo(long GameId) { try @@ -1062,6 +1077,6 @@ namespace gaseous_server.Controllers { return NotFound(); } - } - } -} + } + } +} diff --git a/gaseous-server/ProcessQueue.cs b/gaseous-server/ProcessQueue.cs index dcff93d..faf92ce 100644 --- a/gaseous-server/ProcessQueue.cs +++ b/gaseous-server/ProcessQueue.cs @@ -9,20 +9,22 @@ namespace gaseous_server public class QueueItem { - public QueueItem(QueueItemType ItemType, int ExecutionInterval) + public QueueItem(QueueItemType ItemType, int ExecutionInterval, bool AllowManualStart = true) { _ItemType = ItemType; _ItemState = QueueItemState.NeverStarted; _LastRunTime = DateTime.UtcNow.AddMinutes(ExecutionInterval); _Interval = ExecutionInterval; + _AllowManualStart = AllowManualStart; } - public QueueItem(QueueItemType ItemType, int ExecutionInterval, List Blocks) + public QueueItem(QueueItemType ItemType, int ExecutionInterval, List Blocks, bool AllowManualStart = true) { _ItemType = ItemType; _ItemState = QueueItemState.NeverStarted; _LastRunTime = DateTime.UtcNow.AddMinutes(ExecutionInterval); _Interval = ExecutionInterval; + _AllowManualStart = AllowManualStart; _Blocks = Blocks; } @@ -34,6 +36,7 @@ namespace gaseous_server private string _LastResult = ""; private string? _LastError = null; private bool _ForceExecute = false; + private bool _AllowManualStart = true; private List _Blocks = new List(); public QueueItemType ItemType => _ItemType; @@ -50,6 +53,7 @@ namespace gaseous_server public string LastResult => _LastResult; public string? LastError => _LastError; public bool Force => _ForceExecute; + public bool AllowManualStart => _AllowManualStart; public List Blocks => _Blocks; public void Execute() @@ -96,6 +100,10 @@ namespace gaseous_server Classes.ImportGame.LibraryScan(); break; + case QueueItemType.CollectionCompiler: + Logging.Log(Logging.LogType.Information, "Timered Event", "Starting Collection Compiler"); + Classes.Collections.CompileCollections(); + break; } } catch (Exception ex) @@ -125,7 +133,8 @@ namespace gaseous_server TitleIngestor, MetadataRefresh, OrganiseLibrary, - LibraryScan + LibraryScan, + CollectionCompiler } public enum QueueItemState diff --git a/gaseous-server/Program.cs b/gaseous-server/Program.cs index 9349454..32e203b 100644 --- a/gaseous-server/Program.cs +++ b/gaseous-server/Program.cs @@ -1,9 +1,11 @@ -using System.Text.Json.Serialization; +using System.Reflection; +using System.Text.Json.Serialization; using gaseous_server; using gaseous_tools; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.OpenApi.Models; Logging.Log(Logging.LogType.Information, "Startup", "Starting Gaseous Server"); @@ -83,7 +85,31 @@ builder.Services.Configure(options => // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +builder.Services.AddSwaggerGen(options => + { + options.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = "Gaseous Server API", + Description = "An API for managing the Gaseous Server", + TermsOfService = new Uri("https://github.com/gaseous-project/gaseous-server"), + Contact = new OpenApiContact + { + Name = "GitHub Repository", + Url = new Uri("https://github.com/gaseous-project/gaseous-server") + }, + License = new OpenApiLicense + { + Name = "Gaseous Server License", + Url = new Uri("https://github.com/gaseous-project/gaseous-server/blob/main/LICENSE") + } + }); + + // using System.Reflection; + var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); + } +); builder.Services.AddHostedService(); var app = builder.Build(); @@ -145,6 +171,7 @@ ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( ProcessQueue.QueueItemType.OrganiseLibrary }) ); +ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(ProcessQueue.QueueItemType.CollectionCompiler, 5, false)); // start the app app.Run(); diff --git a/gaseous-server/Support/PlatformMap.json b/gaseous-server/Support/PlatformMap.json index 7943952..66574dd 100644 --- a/gaseous-server/Support/PlatformMap.json +++ b/gaseous-server/Support/PlatformMap.json @@ -87,7 +87,15 @@ ], "WebEmulator": { "Type": "EmulatorJS", - "Core": "segaMD" + "Core": "segaMD", + "Bios": [ + { + "hash": "45e298905a08f9cfb38fd504cd6dbc84", + "description": "MegaDrive TMSS startup ROM", + "filename": "bios_MD.bin", + "region": "" + } + ] } }, { @@ -98,7 +106,9 @@ "N64" ], "KnownFileExtensions": [ - ".Z64" + ".Z64", + ".V64", + ".N64" ], "WebEmulator": { "Type": "EmulatorJS", @@ -110,20 +120,32 @@ "IGDBName": "Nintendo Entertainment System", "AlternateNames": [ "Nintendo Entertainment System", - "NES" + "NES", + "Nintendo Famicom & Entertainment System" ], "KnownFileExtensions": [ ".NES", - ".FDS", - ".FIG", - ".MGD", - ".SFC", - ".SMC", - ".SWC" + ".NEZ", + ".UNF", + ".UNIF" ], "WebEmulator": { "Type": "EmulatorJS", - "Core": "nes" + "Core": "nes", + "Bios": [ + { + "hash": "ca30b50f880eb660a320674ed365ef7a", + "description": "Family Computer Disk System BIOS - Required for Famicom Disk System emulation", + "filename": "disksys.rom", + "region": "" + }, + { + "hash": "7f98d77d7a094ad7d069b74bd553ec98", + "description": "Game Genie add-on cartridge - Required for Game Genei Add-on emulation (Only supported on the fceumm core)", + "filename": "gamegenie.nes", + "region": "" + } + ] } }, { @@ -135,7 +157,10 @@ "Super Nintendo", "SNES" ], - "KnownFileExtensions": [], + "KnownFileExtensions": [ + ".SFC", + ".SMC" + ], "WebEmulator": { "Type": "EmulatorJS", "Core": "snes" diff --git a/gaseous-server/gaseous-server.csproj b/gaseous-server/gaseous-server.csproj index 3e376f7..ea14777 100644 --- a/gaseous-server/gaseous-server.csproj +++ b/gaseous-server/gaseous-server.csproj @@ -14,9 +14,9 @@ bin\Debug\net7.0\gaseous-server.xml - + - + @@ -88,24 +88,21 @@ - - - - - - - - + + + + PreserveNewest + @@ -118,13 +115,6 @@ - - - - - - - true diff --git a/gaseous-server/wwwroot/emulators/EmulatorJS b/gaseous-server/wwwroot/emulators/EmulatorJS index 8c5def7..7a4dbc2 160000 --- a/gaseous-server/wwwroot/emulators/EmulatorJS +++ b/gaseous-server/wwwroot/emulators/EmulatorJS @@ -1 +1 @@ -Subproject commit 8c5def77a094dd2fc38f3d17dd7c909c140eb2f4 +Subproject commit 7a4dbc2fd190870addfb52811136e659e78d832c diff --git a/gaseous-server/wwwroot/images/CollectionsWallpaper.jpg b/gaseous-server/wwwroot/images/CollectionsWallpaper.jpg new file mode 100644 index 0000000..77c645f Binary files /dev/null and b/gaseous-server/wwwroot/images/CollectionsWallpaper.jpg differ diff --git a/gaseous-server/wwwroot/images/IGDB_logo.svg b/gaseous-server/wwwroot/images/IGDB_logo.svg new file mode 100644 index 0000000..5ffe7b4 --- /dev/null +++ b/gaseous-server/wwwroot/images/IGDB_logo.svg @@ -0,0 +1,11 @@ + + +IGDB logo + + \ No newline at end of file diff --git a/gaseous-server/wwwroot/images/TOSEC_logo.gif b/gaseous-server/wwwroot/images/TOSEC_logo.gif new file mode 100644 index 0000000..03995aa Binary files /dev/null and b/gaseous-server/wwwroot/images/TOSEC_logo.gif differ diff --git a/gaseous-server/wwwroot/images/collections.svg b/gaseous-server/wwwroot/images/collections.svg new file mode 100644 index 0000000..3e63453 --- /dev/null +++ b/gaseous-server/wwwroot/images/collections.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/gaseous-server/wwwroot/images/delete.svg b/gaseous-server/wwwroot/images/delete.svg new file mode 100644 index 0000000..e918f1e --- /dev/null +++ b/gaseous-server/wwwroot/images/delete.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/gaseous-server/wwwroot/images/download.svg b/gaseous-server/wwwroot/images/download.svg new file mode 100644 index 0000000..85984a3 --- /dev/null +++ b/gaseous-server/wwwroot/images/download.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/gaseous-server/wwwroot/images/edit.svg b/gaseous-server/wwwroot/images/edit.svg new file mode 100644 index 0000000..7d955aa --- /dev/null +++ b/gaseous-server/wwwroot/images/edit.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/gaseous-server/wwwroot/images/gamebg1.jpg b/gaseous-server/wwwroot/images/gamebg1.jpg new file mode 100644 index 0000000..25d6dbb Binary files /dev/null and b/gaseous-server/wwwroot/images/gamebg1.jpg differ diff --git a/gaseous-server/wwwroot/images/gamebg2.jpg b/gaseous-server/wwwroot/images/gamebg2.jpg new file mode 100644 index 0000000..2f16990 Binary files /dev/null and b/gaseous-server/wwwroot/images/gamebg2.jpg differ diff --git a/gaseous-server/wwwroot/images/gamebg3.jpg b/gaseous-server/wwwroot/images/gamebg3.jpg new file mode 100644 index 0000000..394e0db Binary files /dev/null and b/gaseous-server/wwwroot/images/gamebg3.jpg differ diff --git a/gaseous-server/wwwroot/images/library.svg b/gaseous-server/wwwroot/images/library.svg new file mode 100644 index 0000000..205dfe4 --- /dev/null +++ b/gaseous-server/wwwroot/images/library.svg @@ -0,0 +1,12 @@ + + + + library + + + + + + + + \ No newline at end of file diff --git a/gaseous-server/wwwroot/images/librarybg.jpg b/gaseous-server/wwwroot/images/librarybg.jpg new file mode 100644 index 0000000..03cb015 Binary files /dev/null and b/gaseous-server/wwwroot/images/librarybg.jpg differ diff --git a/gaseous-server/wwwroot/images/refresh.svg b/gaseous-server/wwwroot/images/refresh.svg new file mode 100644 index 0000000..96433b6 --- /dev/null +++ b/gaseous-server/wwwroot/images/refresh.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/gaseous-server/wwwroot/images/unknowngame.png b/gaseous-server/wwwroot/images/unknowngame.png index 073f4a8..78b8573 100644 Binary files a/gaseous-server/wwwroot/images/unknowngame.png and b/gaseous-server/wwwroot/images/unknowngame.png differ diff --git a/gaseous-server/wwwroot/index.html b/gaseous-server/wwwroot/index.html index 5d11070..8c09c26 100644 --- a/gaseous-server/wwwroot/index.html +++ b/gaseous-server/wwwroot/index.html @@ -25,15 +25,29 @@ - - +
diff --git a/gaseous-server/wwwroot/pages/EmulatorJS.html b/gaseous-server/wwwroot/pages/EmulatorJS.html index f56185f..609a029 100644 --- a/gaseous-server/wwwroot/pages/EmulatorJS.html +++ b/gaseous-server/wwwroot/pages/EmulatorJS.html @@ -23,5 +23,8 @@ EJS_DEBUG_XX = false; EJS_startOnLoaded = false; + + EJS_backgroundImage = emuBackground; + EJS_backgroundBlur = true; \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/collections.html b/gaseous-server/wwwroot/pages/collections.html new file mode 100644 index 0000000..6ec71b1 --- /dev/null +++ b/gaseous-server/wwwroot/pages/collections.html @@ -0,0 +1,80 @@ +
+
+
+ +
+
+

Collections

+
+ +
+ +
+
+ +
+ Wallpaper by andrea16 / Wallpaper Cave +
+ + \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/dialogs/collectiondelete.html b/gaseous-server/wwwroot/pages/dialogs/collectiondelete.html new file mode 100644 index 0000000..4131d0b --- /dev/null +++ b/gaseous-server/wwwroot/pages/dialogs/collectiondelete.html @@ -0,0 +1,27 @@ +

Are you sure you want to delete this collection?

+

Warning: This cannot be undone!

+
+
+ +
+
+ +
+
+ + \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/dialogs/collectionedit.html b/gaseous-server/wwwroot/pages/dialogs/collectionedit.html new file mode 100644 index 0000000..d099f0e --- /dev/null +++ b/gaseous-server/wwwroot/pages/dialogs/collectionedit.html @@ -0,0 +1,494 @@ + + + +
+
+ + + + +
+

Filter

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Platforms
Genres
Players
Player Perspectives
Themes
Rating + + +
+

Options

+
Maximum ROMs per platform
Maximum size per platform (bytes)
Maximum collection size (bytes)
+
+ + + + +
+

Collection

+
+
+
+ +
+
+
+ +
+
+ + + +
+
+
+ + \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/emulator.html b/gaseous-server/wwwroot/pages/emulator.html index a9cca57..2563af0 100644 --- a/gaseous-server/wwwroot/pages/emulator.html +++ b/gaseous-server/wwwroot/pages/emulator.html @@ -12,6 +12,7 @@ var artworksPosition = 0; var emuBios = ''; + var emuBackground = ''; ajaxCall('/api/v1/Games/' + gameId, 'GET', function (result) { gameData = result; @@ -28,6 +29,10 @@ bg.setAttribute('style', 'background-image: url("/api/v1/Games/' + gameId + '/cover/image"); background-position: center; background-repeat: no-repeat; background-size: cover; filter: blur(10px); -webkit-filter: blur(10px);'); } } + + if (result.cover) { + emuBackground = '/api/v1/Games/' + gameId + '/cover/image'; + } }); ajaxCall('/api/v1/Bios/' + platformId, 'GET', function (result) { diff --git a/gaseous-server/wwwroot/pages/game.html b/gaseous-server/wwwroot/pages/game.html index 4262caf..2d8b2e6 100644 --- a/gaseous-server/wwwroot/pages/game.html +++ b/gaseous-server/wwwroot/pages/game.html @@ -2,11 +2,26 @@
+ + +
- - +
+ + +

@@ -65,9 +80,8 @@
- - + \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/home.html b/gaseous-server/wwwroot/pages/home.html index 1ef29ae..e45467d 100644 --- a/gaseous-server/wwwroot/pages/home.html +++ b/gaseous-server/wwwroot/pages/home.html @@ -1,4 +1,8 @@ -
+
+
+
+ +
diff --git a/gaseous-server/wwwroot/pages/settings/about.html b/gaseous-server/wwwroot/pages/settings/about.html index e2c8a19..16fa1f2 100644 --- a/gaseous-server/wwwroot/pages/settings/about.html +++ b/gaseous-server/wwwroot/pages/settings/about.html @@ -2,7 +2,7 @@

About Gaseous

- +
@@ -11,4 +11,31 @@ + + + + + + + + + + + + + +
Home Page https://github.com/gaseous-project/gaseous-serverBugs and Feature Requests https://github.com/gaseous-project/gaseous-server/issues
+

Data Sources

+

Game data

+
+ + + The Internet Game Database +
+

Signature data sources

+
+ + + The Old School Emulation Center +
\ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/settings/system.html b/gaseous-server/wwwroot/pages/settings/system.html index 9643a60..996d7dc 100644 --- a/gaseous-server/wwwroot/pages/settings/system.html +++ b/gaseous-server/wwwroot/pages/settings/system.html @@ -51,6 +51,9 @@ case 'LibraryScan': itemTypeName = "Library scan"; break; + case 'CollectionCompiler': + itemTypeName = "Compress collections"; + break; default: itemTypeName = result[i].itemType; break; @@ -78,7 +81,7 @@ } var startButton = ''; - if (result[i].itemState != "Running") { + if (result[i].allowManualStart == true && result[i].itemState != "Running") { startButton = "Start"; } diff --git a/gaseous-server/wwwroot/scripts/main.js b/gaseous-server/wwwroot/scripts/main.js index a46d9e6..3d6dc43 100644 --- a/gaseous-server/wwwroot/scripts/main.js +++ b/gaseous-server/wwwroot/scripts/main.js @@ -1,4 +1,4 @@ -function ajaxCall(endpoint, method, successFunction) { +function ajaxCall(endpoint, method, successFunction, errorFunction, body) { $.ajax({ // Our sample url to make request @@ -8,17 +8,27 @@ // Type of Request type: method, + // data to send to the server + data: body, + + dataType: 'json', + contentType: 'application/json', + // Function to call when to // request is ok success: function (data) { - var x = JSON.stringify(data); - console.log(x); + //var x = JSON.stringify(data); + //console.log(x); successFunction(data); }, // Error handling error: function (error) { - console.log(`Error ${error}`); + console.log(`Error ${JSON.stringify(error)}`); + + if (errorFunction) { + errorFunction(error); + } } }); } @@ -96,6 +106,18 @@ function showDialog(dialogPage, variables) { $('#modal-content').load('/pages/dialogs/' + dialogPage + '.html'); } +function closeDialog() { + // Get the modal + var modal = document.getElementById("myModal"); + + // Get the modal content + var modalContent = document.getElementById("modal-content"); + + modal.style.display = "none"; + modalContent.innerHTML = ""; + modalVariables = null; +} + var subModalVariables; function showSubDialog(dialogPage, variables) { @@ -121,7 +143,7 @@ function showSubDialog(dialogPage, variables) { subModalVariables = null; } - subModalVariables = modalVariables; + subModalVariables = variables; $('#modal-content-sub').load('/pages/dialogs/' + dialogPage + '.html'); } diff --git a/gaseous-server/wwwroot/styles/style.css b/gaseous-server/wwwroot/styles/style.css index 955d0de..daf6336 100644 --- a/gaseous-server/wwwroot/styles/style.css +++ b/gaseous-server/wwwroot/styles/style.css @@ -70,6 +70,9 @@ h3 { border-image: linear-gradient(to right, rgba(255,0,0,1) 0%, rgba(251,255,0,1) 16%, rgba(0,255,250,1) 30%, rgba(0,16,255,1) 46%, rgba(250,0,255,1) 62%, rgba(255,0,0,1) 78%, rgba(255,237,0,1) 90%, rgba(20,255,0,1) 100%) 5; } +#modal-content { + height: 100%; +} /* The Close Button */ .close { @@ -109,11 +112,10 @@ h3 { height: 30px; } -#banner_upload { - background-color: white; - position: fixed; +.banner_button { + background-color: transparent; + float: right; top: 0px; - right: 41px; height: 40px; align-items: center; justify-content: center; @@ -121,44 +123,24 @@ h3 { padding-right: 10px; padding-top: 0px; padding-bottom: 0px; - margin: 0px; + margin: 0px 0px 0px 0px; display: flex; - color: black; + color: white; + border-left-color: black; + border-left-width: 1px; + border-left-style: solid; } -#banner_upload:hover { +.banner_button:hover { cursor: pointer; - background-color: lightgrey; + background-color: black; } -#banner_cog { - background-color: white; - position: fixed; - top: 0px; - right: 0px; - width: 40px; - height: 40px; - align-items: center; - justify-content: center; - padding: 0px; - margin: 0px; - display: flex; -} - -#banner_cog:hover { - cursor: pointer; - background-color: lightgrey; -} - -#banner_system_image { - height: 20px; - width: 20px; -} - -#banner_upload_image { +.banner_button_image { height: 20px; width: 20px; margin-right: 5px; + filter: invert(100%); } #banner_header { @@ -171,12 +153,11 @@ h3 { height: 40px; right: 0px; align-items: center; - display: flex; } #banner_header_label { font-family: Commodore64; - display: inline; + display: inline-block; padding: 10px; font-size: 16pt; vertical-align: top; @@ -184,6 +165,10 @@ h3 { color: #7c70da; } +#banner_header_label { + cursor: pointer; +} + #content { margin-top: 35px; padding-top: 5px; @@ -327,6 +312,11 @@ input[id='filter_panel_userrating_max'] { background-color: white; } +.game_tile_image_small { + max-width: 100px; + max-height: 100px; +} + #bgImage { position: fixed; top: -30px; @@ -640,6 +630,11 @@ div[name="properties_toc_item"]:hover { margin-top: 20px; } +.select2-container--default .select2-selection--multiple { + background-color: #2b2b2b !important; + color: white !important; +} + .select2-container--open .select2-dropdown--below, .select2-container--open .select2-dropdown--above { background: #2b2b2b; @@ -649,6 +644,29 @@ div[name="properties_toc_item"]:hover { border: 1px solid #2b2b2b; } +.select2-selection__choice { + background-color: #2b2b2b !important; +} + +.select2-selection__choice__display { + color: white; +} + +.select2-results__option--selected { + background-color: black !important; +} + +.select2-selection--single .select2-selection__rendered { + background-color: #2b2b2b; + color: white !important; + border: 1px solid #2b2b2b; +} + +.select2-search__field { + background-color: #2b2b2b; + color: white; +} + .dropdown-div { width: 100%; } @@ -812,4 +830,51 @@ button:disabled { #gametitle_criticrating_label { display: inline-block; text-align: left; +} + +#rom_edit_progressbar { + width: 100%; + height: 10px; + background-color: lightgray; + margin-top: 10px; +} + +#rom_edit_progressbar_progress { + height: 10px; + background-color: cyan; +} + +.collections_modal { + width: 1000px; + height: 90%; + margin: 25px auto; +/* overflow-x: scroll;*/ + position: relative; +} + +.collections_preview_platform_header { + background-color: #2b2b2b; +} + +.collections_preview_gamecovercell { + text-align: center; + width: 110px; + vertical-align: top; + padding-bottom: 10px; +} + +.collections_preview_gametitlecell { + font-weight: bold; +} + +.collections_preview_gamedetailcell { + vertical-align: top; +} + +.bgalt0 { + background-color: #414040; +} + +.bgalt1 { + background-color: transparent;; } \ No newline at end of file diff --git a/gaseous-tools/Common.cs b/gaseous-tools/Common.cs index 9bae0ab..24cc8f9 100644 --- a/gaseous-tools/Common.cs +++ b/gaseous-tools/Common.cs @@ -13,7 +13,7 @@ namespace gaseous_tools /// static public object ReturnValueIfNull(object? ObjectToCheck, object IfNullValue) { - if (ObjectToCheck == null) + if (ObjectToCheck == null || ObjectToCheck == System.DBNull.Value) { return IfNullValue; } else diff --git a/gaseous-tools/Config.cs b/gaseous-tools/Config.cs index 1ec0c02..558936a 100644 --- a/gaseous-tools/Config.cs +++ b/gaseous-tools/Config.cs @@ -359,6 +359,22 @@ namespace gaseous_tools } } + public string LibraryTempDirectory + { + get + { + return Path.Combine(LibraryRootDirectory, "Temp"); + } + } + + public string LibraryCollectionsDirectory + { + get + { + return Path.Combine(LibraryRootDirectory, "Collections"); + } + } + public string LibraryMetadataDirectory_Platform(Platform platform) { string MetadataPath = Path.Combine(LibraryMetadataDirectory, "Platforms", platform.Slug); @@ -404,6 +420,8 @@ namespace gaseous_tools if (!Directory.Exists(LibraryBIOSDirectory)) { Directory.CreateDirectory(LibraryBIOSDirectory); } if (!Directory.Exists(LibraryUploadDirectory)) { Directory.CreateDirectory(LibraryUploadDirectory); } if (!Directory.Exists(LibraryMetadataDirectory)) { Directory.CreateDirectory(LibraryMetadataDirectory); } + if (!Directory.Exists(LibraryTempDirectory)) { Directory.CreateDirectory(LibraryTempDirectory); } + if (!Directory.Exists(LibraryCollectionsDirectory)) { Directory.CreateDirectory(LibraryCollectionsDirectory); } if (!Directory.Exists(LibrarySignatureImportDirectory)) { Directory.CreateDirectory(LibrarySignatureImportDirectory); } if (!Directory.Exists(LibrarySignatureImportDirectory_TOSEC)) { Directory.CreateDirectory(LibrarySignatureImportDirectory_TOSEC); } } diff --git a/gaseous-tools/Database/MySQL/gaseous-1001.sql b/gaseous-tools/Database/MySQL/gaseous-1001.sql index 421dd99..3e2204e 100644 --- a/gaseous-tools/Database/MySQL/gaseous-1001.sql +++ b/gaseous-tools/Database/MySQL/gaseous-1001.sql @@ -62,4 +62,25 @@ CREATE TABLE `Theme` ( `dateAdded` datetime DEFAULT NULL, `lastUpdated` datetime DEFAULT NULL, PRIMARY KEY (`Id`) -); \ No newline at end of file +); + +DROP TABLE IF EXISTS `RomCollections`; +CREATE TABLE `RomCollections` ( + `Id` BIGINT NOT NULL AUTO_INCREMENT, + `Name` VARCHAR(255) NULL, + `Description` LONGTEXT NULL, + `Platforms` JSON NULL, + `Genres` JSON NULL, + `Players` JSON NULL, + `PlayerPerspectives` JSON NULL, + `Themes` JSON NULL, + `MinimumRating` INT NULL, + `MaximumRating` INT NULL, + `MaximumRomsPerPlatform` INT NULL, + `MaximumBytesPerPlatform` BIGINT NULL, + `MaximumCollectionSizeInBytes` BIGINT NULL, + `BuiltStatus` INT NULL, + PRIMARY KEY (`Id`)); + +ALTER TABLE `gaseous`.`Signatures_Sources` +CHANGE COLUMN `Author` `Author` LONGTEXT NULL DEFAULT NULL ; diff --git a/screenshots/Emulator.png b/screenshots/Emulator.png new file mode 100644 index 0000000..d0efb58 Binary files /dev/null and b/screenshots/Emulator.png differ diff --git a/screenshots/Game.png b/screenshots/Game.png index b80469d..31a2e91 100644 Binary files a/screenshots/Game.png and b/screenshots/Game.png differ diff --git a/screenshots/Library.png b/screenshots/Library.png index 78a4f35..28734ac 100644 Binary files a/screenshots/Library.png and b/screenshots/Library.png differ