diff --git a/README.MD b/README.MD index 56440e7..9f75b4a 100644 --- a/README.MD +++ b/README.MD @@ -73,55 +73,8 @@ When Gaseous-Server is started for the first time, it creates a configuration fi ``` -## Docker -### Deploy with the prebuilt Docker image -Dockerfile and docker-compose.yml files have been provided to make deployment of the server as easy as possible. -1. Download the docker-compose-{database}.yml file for the database type you would like to use. -2. Open the docker-compose.yml file and edit the igdbclientid and igdbclientsecret to the values retrieved from your IGDB account -3. Run the command ```docker-compose up -d``` -4. Connect to the host on port 5198 - -### Build and deploy a Docker image from source -Dockerfile and docker-compose-build.yml files have been provided to make deployment of the server as easy as possible. -1. Clone the repo with ```git clone https://github.com/gaseous-project/gaseous-server.git``` -2. Change into the gaseous-server directory -3. Open the docker-compose-{database}-build.yml file and edit the igdbclientid and igdbclientsecret to the values retrieved from your IGDB account -4. Run the command ```docker-compose --file docker-compose-{database}-build.yml up -d``` -5. Connect to the host on port 5198 - -## Source -### Build and deploy -1. Install and configure a MariaDB or MySQL instance - this is beyond the scope of this document -2. Install the dotnet 7.0 packages appropriate for your operating system - * See: https://learn.microsoft.com/en-us/dotnet/core/install/linux -3. Create a database user with permission to create a databse. Gaseous will create the new database and apply the database schema on it's first startup. -4. Clone the repo with ```git clone https://github.com/gaseous-project/gaseous-server.git``` -5. Change into the gaseous-server directory -6. As the main branch is the development branch, you might want to change to a stable version - these are tagged with a version number. For example to change to the 1.5.0 release, use the command ```git checkout v1.5.0``` - * Check the releases page for the version you would like to run: https://github.com/gaseous-project/gaseous-server/releases -7. Download the emulator files from ```https://cdn.emulatorjs.org/releases/4.0.9.zip``` and extract the files to ```gaseous-server/wwwroot/emulators/EmulatorJS``` -8. Create a directory in the home directory of the user that will run the server. For example, if running as the user ```gaseous```, create the directory ```/home/gaseous/.gaseous-server``` -9. Change into the ```.gaseous-server``` directory created in the previous step -10. Copy the JSON from the config file above into a new file named ```config.json``` -11. Update the database section with the database server hostname, username, password, and port -12. Compile the server by changing back to the repo cloned earlier and executing: - * ```dotnet restore "gaseous-server/gaseous-server.csproj"``` - * ```dotnet publish "gaseous-server/gaseous-server.csproj" --use-current-runtime --self-contained false -c Release -o ``` - * replace `````` with the directory of your choosing. The compiled application will be copied there. For this example we'll use ```/opt/gaseous-server``` -13. The server can then be started by executing ```dotnet /opt/gaseous-server/gaseous-server.dll``` - * If you would like the server to run on a different ip address and port (for example 0.0.0.0:8080), add the --urls argument: ```dotnet /opt/gaseous-server/gaseous-server.dll --urls http://0.0.0.0:8080``` - -**Note**: The above instructions were tested on macOS Ventura, and Ubuntu 22.04.3. There was a report that Debian 11 had an issue with the git submodule commands (see: https://github.com/gaseous-project/gaseous-server/issues/71). This was possibly due to an older git package. - -### Updating from source -1. Stop the server -2. Switch to the source directory -3. Update your repo: - * If running from the main branch, run ```git pull``` to update the repo - * If running from another branch or tag, run: - * ```git fetch``` - * ```git checkout ``` -4. Run steps 12 and 13 from the above Build guide +# Installation +See https://github.com/gaseous-project/gaseous-server/wiki/Installation for installation instructions. # Adding Content While games can be added to the server without them, it is recommended adding some signature DAT files beforehand to allow for better matching of ROMs to games. diff --git a/gaseous-server/Classes/Common.cs b/gaseous-server/Classes/Common.cs index 9ba5d2f..e1e3dff 100644 --- a/gaseous-server/Classes/Common.cs +++ b/gaseous-server/Classes/Common.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.ComponentModel; +using System.IO.Compression; using System.Reflection; using System.Security.Cryptography; @@ -120,6 +121,28 @@ namespace gaseous_server.Classes .Single(x => x.GetValue(null).Equals(value)), typeof(DescriptionAttribute)))?.Description ?? value.ToString(); } + + // compression + public static byte[] Compress(byte[] data) + { + MemoryStream output = new MemoryStream(); + using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Optimal)) + { + dstream.Write(data, 0, data.Length); + } + return output.ToArray(); + } + + public static byte[] Decompress(byte[] data) + { + MemoryStream input = new MemoryStream(data); + MemoryStream output = new MemoryStream(); + using (DeflateStream dstream = new DeflateStream(input, CompressionMode.Decompress)) + { + dstream.CopyTo(output); + } + return output.ToArray(); + } } /// diff --git a/gaseous-server/Classes/Config.cs b/gaseous-server/Classes/Config.cs index 77d97a4..94b2383 100644 --- a/gaseous-server/Classes/Config.cs +++ b/gaseous-server/Classes/Config.cs @@ -3,6 +3,7 @@ using System.Data; using Newtonsoft.Json; using IGDB.Models; using gaseous_server.Classes.Metadata; +using NuGet.Common; namespace gaseous_server.Classes { @@ -161,7 +162,7 @@ namespace gaseous_server.Classes File.WriteAllText(ConfigurationFilePath, configRaw); } - private static Dictionary AppSettings = new Dictionary(); + private static Dictionary AppSettings = new Dictionary(); public static void InitSettings() { @@ -173,43 +174,67 @@ namespace gaseous_server.Classes { if (AppSettings.ContainsKey((string)dataRow["Setting"])) { - AppSettings[(string)dataRow["Setting"]] = (string)dataRow["Value"]; + if ((int)dataRow["ValueType"] == 0) + { + AppSettings[(string)dataRow["Setting"]] = (string)dataRow["Value"]; + } + else + { + AppSettings[(string)dataRow["Setting"]] = (DateTime)dataRow["ValueDate"]; + } } else { - AppSettings.Add((string)dataRow["Setting"], (string)dataRow["Value"]); + if ((int)dataRow["ValueType"] == 0) + { + AppSettings.Add((string)dataRow["Setting"], (string)dataRow["Value"]); + } + else + { + AppSettings.Add((string)dataRow["Setting"], (DateTime)dataRow["ValueDate"]); + } } } } - public static string ReadSetting(string SettingName, string DefaultValue) + public static T ReadSetting(string SettingName, T DefaultValue) { if (AppSettings.ContainsKey(SettingName)) { - return AppSettings[SettingName]; + return (T)AppSettings[SettingName]; } else { Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "SELECT Value FROM Settings WHERE Setting = @SettingName"; - Dictionary dbDict = new Dictionary(); - dbDict.Add("SettingName", SettingName); - dbDict.Add("Value", DefaultValue); + string sql = "SELECT Value, ValueDate FROM Settings WHERE Setting = @SettingName"; + Dictionary dbDict = new Dictionary + { + { "SettingName", SettingName } + }; try { Logging.Log(Logging.LogType.Debug, "Database", "Reading setting '" + SettingName + "'"); DataTable dbResponse = db.ExecuteCMD(sql, dbDict); + Type type = typeof(T); if (dbResponse.Rows.Count == 0) { // no value with that name stored - respond with the default value - SetSetting(SettingName, DefaultValue); + SetSetting(SettingName, DefaultValue); return DefaultValue; } else { - AppSettings.Add(SettingName, (string)dbResponse.Rows[0][0]); - return (string)dbResponse.Rows[0][0]; + if (type.ToString() == "System.DateTime") + { + AppSettings.Add(SettingName, dbResponse.Rows[0]["ValueDate"]); + return (T)dbResponse.Rows[0]["ValueDate"]; + } + else + { + AppSettings.Add(SettingName, dbResponse.Rows[0]["Value"]); + return (T)dbResponse.Rows[0]["Value"]; + } } } catch (Exception ex) @@ -220,13 +245,32 @@ namespace gaseous_server.Classes } } - public static void SetSetting(string SettingName, string Value) + public static void SetSetting(string SettingName, T Value) { Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "REPLACE INTO Settings (Setting, Value) VALUES (@SettingName, @Value)"; - Dictionary dbDict = new Dictionary(); - dbDict.Add("SettingName", SettingName); - dbDict.Add("Value", Value); + string sql = "REPLACE INTO Settings (Setting, ValueType, Value, ValueDate) VALUES (@SettingName, @ValueType, @Value, @ValueDate)"; + Dictionary dbDict; + Type type = typeof(T); + if (type.ToString() == "System.DateTime") + { + dbDict = new Dictionary + { + { "SettingName", SettingName }, + { "ValueType", 1 }, + { "Value", null }, + { "ValueDate", Value } + }; + } + else + { + dbDict = new Dictionary + { + { "SettingName", SettingName }, + { "ValueType", 0 }, + { "Value", Value }, + { "ValueDate", null } + }; + } Logging.Log(Logging.LogType.Debug, "Database", "Storing setting '" + SettingName + "' to value: '" + Value + "'"); try @@ -341,11 +385,11 @@ namespace gaseous_server.Classes { get { - return ReadSetting("LibraryRootDirectory", Path.Combine(Config.ConfigurationPath, "Data")); + return ReadSetting("LibraryRootDirectory", Path.Combine(Config.ConfigurationPath, "Data")); } set { - SetSetting("LibraryRootDirectory", value); + SetSetting("LibraryRootDirectory", value); } } diff --git a/gaseous-server/Classes/ImportGames.cs b/gaseous-server/Classes/ImportGames.cs index f519233..d8d9701 100644 --- a/gaseous-server/Classes/ImportGames.cs +++ b/gaseous-server/Classes/ImportGames.cs @@ -452,13 +452,15 @@ namespace gaseous_server.Classes if (romDT.Rows.Count > 0) { - foreach (DataRow dr in romDT.Rows) + for (int i = 0; i < romDT.Rows.Count; i++) { - Logging.Log(Logging.LogType.Information, "Organise Library", "Processing ROM " + dr["name"]); - long RomId = (long)dr["id"]; + SetStatus(i, romDT.Rows.Count, "Processing file " + romDT.Rows[i]["name"]); + Logging.Log(Logging.LogType.Information, "Organise Library", "(" + i + "/" + romDT.Rows.Count + ") Processing ROM " + romDT.Rows[i]["name"]); + long RomId = (long)romDT.Rows[i]["id"]; MoveGameFile(RomId); } } + ClearStatus(); // clean up empty directories DeleteOrphanedDirectories(GameLibrary.GetDefaultLibrary.Path); diff --git a/gaseous-server/Classes/Maintenance.cs b/gaseous-server/Classes/Maintenance.cs index 652e12f..a2db20b 100644 --- a/gaseous-server/Classes/Maintenance.cs +++ b/gaseous-server/Classes/Maintenance.cs @@ -9,7 +9,7 @@ namespace gaseous_server.Classes { const int MaxFileAge = 30; - public void RunMaintenance() + public void RunDailyMaintenance() { Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); string sql = ""; @@ -33,8 +33,8 @@ namespace gaseous_server.Classes } // delete old logs - sql = "DELETE FROM ServerLogs WHERE EventTime < @EventRententionDate;"; - dbDict.Add("EventRententionDate", DateTime.UtcNow.AddDays(Config.LoggingConfiguration.LogRetention * -1)); + sql = "DELETE FROM ServerLogs WHERE EventTime < @EventRetentionDate;"; + dbDict.Add("EventRetentionDate", DateTime.UtcNow.AddDays(Config.LoggingConfiguration.LogRetention * -1)); db.ExecuteCMD(sql, dbDict); // delete files and directories older than 7 days in PathsToClean @@ -69,6 +69,13 @@ namespace gaseous_server.Classes } } } + } + + public void RunWeeklyMaintenance() + { + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = ""; + Dictionary dbDict = new Dictionary(); Logging.Log(Logging.LogType.Information, "Maintenance", "Optimising database tables"); sql = "SHOW FULL TABLES WHERE Table_Type = 'BASE TABLE';"; diff --git a/gaseous-server/Controllers/V1.0/SystemController.cs b/gaseous-server/Controllers/V1.0/SystemController.cs index d285dde..4cba7ff 100644 --- a/gaseous-server/Controllers/V1.0/SystemController.cs +++ b/gaseous-server/Controllers/V1.0/SystemController.cs @@ -12,6 +12,7 @@ using gaseous_server.Classes.Metadata; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Razor.Hosting; +using RestEase; namespace gaseous_server.Controllers { @@ -96,7 +97,7 @@ namespace gaseous_server.Controllers string ver = "var AppVersion = \"" + Assembly.GetExecutingAssembly().GetName().Version.ToString() + "\";" + Environment.NewLine + "var DBSchemaVersion = \"" + db.GetDatabaseSchemaVersion() + "\";" + Environment.NewLine + - "var FirstRunStatus = " + Config.ReadSetting("FirstRunStatus", "0") + ";" + Environment.NewLine + + "var FirstRunStatus = " + Config.ReadSetting("FirstRunStatus", "0") + ";" + Environment.NewLine + "var AgeRatingBoardsStrings = " + JsonSerializer.Serialize(ClassificationBoardsStrings, new JsonSerializerOptions{ WriteIndented = true }) + ";" + Environment.NewLine + @@ -113,19 +114,23 @@ namespace gaseous_server.Controllers [MapToApiVersion("1.0")] [MapToApiVersion("1.1")] [HttpGet] - [Route("Settings/BackgroundTasks/Intervals")] + [Route("Settings/BackgroundTasks/Configuration")] [Authorize(Roles = "Admin")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetBackgroundTasks() { Dictionary Intervals = new Dictionary(); - Intervals.Add(ProcessQueue.QueueItemType.SignatureIngestor.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.SignatureIngestor)); - Intervals.Add(ProcessQueue.QueueItemType.TitleIngestor.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.TitleIngestor)); - Intervals.Add(ProcessQueue.QueueItemType.MetadataRefresh.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.MetadataRefresh)); - Intervals.Add(ProcessQueue.QueueItemType.OrganiseLibrary.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.OrganiseLibrary)); - Intervals.Add(ProcessQueue.QueueItemType.LibraryScan.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.LibraryScan)); - Intervals.Add(ProcessQueue.QueueItemType.Rematcher.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.Rematcher)); - Intervals.Add(ProcessQueue.QueueItemType.Maintainer.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.Maintainer)); + foreach (ProcessQueue.QueueItemType itemType in Enum.GetValues(typeof(ProcessQueue.QueueItemType))) + { + BackgroundTaskItem taskItem = new BackgroundTaskItem(itemType); + if (taskItem.UserManageable == true) + { + if (!Intervals.ContainsKey(itemType.ToString())) + { + Intervals.Add(itemType.ToString(), taskItem); + } + } + } return Ok(Intervals); } @@ -133,45 +138,102 @@ namespace gaseous_server.Controllers [MapToApiVersion("1.0")] [MapToApiVersion("1.1")] [HttpPost] - [Route("Settings/BackgroundTasks/Intervals")] + [Route("Settings/BackgroundTasks/Configuration")] [Authorize(Roles = "Admin")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult SetBackgroundTasks(Dictionary Intervals) + public ActionResult SetBackgroundTasks([FromBody] List model) { - foreach (KeyValuePair Interval in Intervals) + foreach (BackgroundTaskSettingsItem TaskConfiguration in model) { - if (Enum.IsDefined(typeof(ProcessQueue.QueueItemType), Interval.Key)) + if (Enum.IsDefined(typeof(ProcessQueue.QueueItemType), TaskConfiguration.Task)) { try { BackgroundTaskItem taskItem = new BackgroundTaskItem( - (ProcessQueue.QueueItemType)Enum.Parse(typeof(ProcessQueue.QueueItemType), Interval.Key) + (ProcessQueue.QueueItemType)Enum.Parse(typeof(ProcessQueue.QueueItemType), TaskConfiguration.Task) ); - if (Interval.Value >= taskItem.MinimumAllowedValue) + if (taskItem.UserManageable == true) { - Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + Interval.Key + " with new interval " + Interval.Value); + // update task enabled + Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with enabled value " + TaskConfiguration.Enabled.ToString()); - Config.SetSetting("Interval_" + Interval.Key, Interval.Value.ToString()); + Config.SetSetting("Enabled_" + TaskConfiguration.Task, TaskConfiguration.Enabled.ToString()); // update existing process foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems) { - if (item.ItemType.ToString().ToLower() == Interval.Key.ToLower()) + if (item.ItemType.ToString().ToLower() == TaskConfiguration.Task.ToLower()) { - item.Interval = Interval.Value; + item.Enabled(Boolean.Parse(TaskConfiguration.Enabled.ToString())); } } + + // update task interval + if (TaskConfiguration.Interval >= taskItem.MinimumAllowedInterval) + { + Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with new interval " + TaskConfiguration.Interval); + + Config.SetSetting("Interval_" + TaskConfiguration.Task, TaskConfiguration.Interval.ToString()); + + // update existing process + foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems) + { + if (item.ItemType.ToString().ToLower() == TaskConfiguration.Task.ToLower()) + { + item.Interval = TaskConfiguration.Interval; + } + } + } + else + { + Logging.Log(Logging.LogType.Warning, "Update Background Task", "Interval " + TaskConfiguration.Interval.ToString() + " for task " + TaskConfiguration.Task + " is below the minimum allowed value of " + taskItem.MinimumAllowedInterval + ". Skipping."); + } + + // update task weekdays + Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with new weekdays " + String.Join(", ", TaskConfiguration.AllowedDays)); + + Config.SetSetting("AllowedDays_" + TaskConfiguration.Task, Newtonsoft.Json.JsonConvert.SerializeObject(TaskConfiguration.AllowedDays)); + + // update existing process + foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems) + { + if (item.ItemType.ToString().ToLower() == TaskConfiguration.Task.ToLower()) + { + item.AllowedDays = TaskConfiguration.AllowedDays; + } + } + + // update task hours + Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with new hours " + TaskConfiguration.AllowedStartHours + ":" + TaskConfiguration.AllowedStartMinutes.ToString("00") + " to " + TaskConfiguration.AllowedEndHours + ":" + TaskConfiguration.AllowedEndMinutes.ToString("00")); + + Config.SetSetting("AllowedStartHours_" + TaskConfiguration.Task, TaskConfiguration.AllowedStartHours.ToString()); + Config.SetSetting("AllowedStartMinutes_" + TaskConfiguration.Task, TaskConfiguration.AllowedStartMinutes.ToString()); + Config.SetSetting("AllowedEndHours_" + TaskConfiguration.Task, TaskConfiguration.AllowedEndHours.ToString()); + Config.SetSetting("AllowedEndMinutes_" + TaskConfiguration.Task, TaskConfiguration.AllowedEndMinutes.ToString()); + + // update existing process + foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems) + { + if (item.ItemType.ToString().ToLower() == TaskConfiguration.Task.ToLower()) + { + item.AllowedStartHours = TaskConfiguration.AllowedStartHours; + item.AllowedStartMinutes = TaskConfiguration.AllowedStartMinutes; + item.AllowedEndHours = TaskConfiguration.AllowedEndHours; + item.AllowedEndMinutes = TaskConfiguration.AllowedEndMinutes; + } + } + } else { - Logging.Log(Logging.LogType.Warning, "Update Background Task", "Interval " + Interval.Value + " for task " + Interval.Key + " is below the minimum allowed value of " + taskItem.MinimumAllowedValue + ". Skipping."); + Logging.Log(Logging.LogType.Warning, "Update Background Task", "Unable to update non-user manageable task " + TaskConfiguration.Task + ". Skipping."); } } catch { // task name not defined - Logging.Log(Logging.LogType.Warning, "Update Background Task", "Task " + Interval.Key + " is not user definable. Skipping."); + Logging.Log(Logging.LogType.Warning, "Update Background Task", "Task " + TaskConfiguration.Task + " is not user definable. Skipping."); } } } @@ -256,61 +318,387 @@ namespace gaseous_server.Controllers public class BackgroundTaskItem { + public BackgroundTaskItem() + { + + } + public BackgroundTaskItem(ProcessQueue.QueueItemType TaskName) { this.Task = TaskName.ToString(); + this.TaskEnum = TaskName; switch (TaskName) { case ProcessQueue.QueueItemType.SignatureIngestor: + this._UserManageable = true; this.DefaultInterval = 60; - this.MinimumAllowedValue = 20; + this.MinimumAllowedInterval = 20; + this.DefaultAllowedDays = new List{ + DayOfWeek.Sunday, + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, + DayOfWeek.Saturday + }; + this.DefaultAllowedStartHours = 0; + this.DefaultAllowedStartMinutes = 0; + this.DefaultAllowedEndHours = 23; + this.DefaultAllowedEndMinutes = 59; break; case ProcessQueue.QueueItemType.TitleIngestor: + this._UserManageable = true; this.DefaultInterval = 1; - this.MinimumAllowedValue = 1; + this.MinimumAllowedInterval = 1; + this.DefaultAllowedDays = new List{ + DayOfWeek.Sunday, + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, + DayOfWeek.Saturday + }; + this.DefaultAllowedStartHours = 0; + this.DefaultAllowedStartMinutes = 0; + this.DefaultAllowedEndHours = 23; + this.DefaultAllowedEndMinutes = 59; + this._Blocks = new List{ + ProcessQueue.QueueItemType.OrganiseLibrary, + ProcessQueue.QueueItemType.LibraryScan, + ProcessQueue.QueueItemType.LibraryScanWorker + }; break; case ProcessQueue.QueueItemType.MetadataRefresh: + this._UserManageable = true; this.DefaultInterval = 360; - this.MinimumAllowedValue = 360; + this.MinimumAllowedInterval = 360; + this.DefaultAllowedDays = new List{ + DayOfWeek.Sunday, + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, + DayOfWeek.Saturday + }; + this.DefaultAllowedStartHours = 0; + this.DefaultAllowedStartMinutes = 0; + this.DefaultAllowedEndHours = 23; + this.DefaultAllowedEndMinutes = 59; break; case ProcessQueue.QueueItemType.OrganiseLibrary: + this._UserManageable = true; this.DefaultInterval = 1440; - this.MinimumAllowedValue = 120; + this.MinimumAllowedInterval = 120; + this.DefaultAllowedDays = new List{ + DayOfWeek.Sunday, + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, + DayOfWeek.Saturday + }; + this.DefaultAllowedStartHours = 0; + this.DefaultAllowedStartMinutes = 0; + this.DefaultAllowedEndHours = 23; + this.DefaultAllowedEndMinutes = 59; + this._Blocks = new List{ + ProcessQueue.QueueItemType.LibraryScan, + ProcessQueue.QueueItemType.LibraryScanWorker, + ProcessQueue.QueueItemType.TitleIngestor, + ProcessQueue.QueueItemType.Rematcher + }; break; case ProcessQueue.QueueItemType.LibraryScan: + this._UserManageable = true; this.DefaultInterval = 1440; - this.MinimumAllowedValue = 120; + this.MinimumAllowedInterval = 120; + this.DefaultAllowedDays = new List{ + DayOfWeek.Sunday, + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, + DayOfWeek.Saturday + }; + this.DefaultAllowedStartHours = 0; + this.DefaultAllowedStartMinutes = 0; + this.DefaultAllowedEndHours = 23; + this.DefaultAllowedEndMinutes = 59; + this._Blocks = new List{ + ProcessQueue.QueueItemType.OrganiseLibrary, + ProcessQueue.QueueItemType.Rematcher + }; break; case ProcessQueue.QueueItemType.Rematcher: + this._UserManageable = true; this.DefaultInterval = 1440; - this.MinimumAllowedValue = 360; + this.MinimumAllowedInterval = 360; + this.DefaultAllowedDays = new List{ + DayOfWeek.Sunday, + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, + DayOfWeek.Saturday + }; + this.DefaultAllowedStartHours = 0; + this.DefaultAllowedStartMinutes = 0; + this.DefaultAllowedEndHours = 23; + this.DefaultAllowedEndMinutes = 59; + this._Blocks = new List{ + ProcessQueue.QueueItemType.OrganiseLibrary, + ProcessQueue.QueueItemType.LibraryScan, + ProcessQueue.QueueItemType.LibraryScanWorker + }; break; - case ProcessQueue.QueueItemType.Maintainer: + case ProcessQueue.QueueItemType.DailyMaintainer: + this._UserManageable = true; + this.DefaultInterval = 1440; + this.MinimumAllowedInterval = 1440; + this.DefaultAllowedDays = new List{ + DayOfWeek.Sunday, + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, + DayOfWeek.Saturday + }; + this.DefaultAllowedStartHours = 1; + this.DefaultAllowedStartMinutes = 0; + this.DefaultAllowedEndHours = 5; + this.DefaultAllowedEndMinutes = 59; + this._Blocks = new List{ + ProcessQueue.QueueItemType.All + }; + break; + + case ProcessQueue.QueueItemType.WeeklyMaintainer: + this._UserManageable = true; this.DefaultInterval = 10080; - this.MinimumAllowedValue = 10080; + this.MinimumAllowedInterval = 10080; + this.DefaultAllowedDays = new List{ + DayOfWeek.Monday + }; + this.DefaultAllowedStartHours = 1; + this.DefaultAllowedStartMinutes = 0; + this.DefaultAllowedEndHours = 5; + this.DefaultAllowedEndMinutes = 59; + this._Blocks = new List{ + ProcessQueue.QueueItemType.All + }; + break; + + case ProcessQueue.QueueItemType.BackgroundDatabaseUpgrade: + this._UserManageable = false; + this.DefaultInterval = 1; + this.MinimumAllowedInterval = 1; + this.DefaultAllowedDays = new List{ + DayOfWeek.Sunday, + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, + DayOfWeek.Saturday + }; + this.DefaultAllowedStartHours = 0; + this.DefaultAllowedStartMinutes = 0; + this.DefaultAllowedEndHours = 23; + this.DefaultAllowedEndMinutes = 59; + this._Blocks.Add(ProcessQueue.QueueItemType.All); + break; + + case ProcessQueue.QueueItemType.TempCleanup: + this._UserManageable = true; + this.DefaultInterval = 1; + this.MinimumAllowedInterval = 1; + this.DefaultAllowedDays = new List{ + DayOfWeek.Sunday, + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, + DayOfWeek.Saturday + }; + this.DefaultAllowedStartHours = 0; + this.DefaultAllowedStartMinutes = 0; + this.DefaultAllowedEndHours = 23; + this.DefaultAllowedEndMinutes = 59; break; default: - throw new Exception("Invalid task"); + this._UserManageable = false; + this.DefaultAllowedDays = new List{ + DayOfWeek.Sunday, + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, + DayOfWeek.Saturday + }; + this.DefaultAllowedStartHours = 0; + this.DefaultAllowedStartMinutes = 0; + this.DefaultAllowedEndHours = 23; + this.DefaultAllowedEndMinutes = 59; + break; } } public string Task { get; set; } + public ProcessQueue.QueueItemType TaskEnum { get; set; } + public bool Enabled + { + get + { + if (_UserManageable == true) + { + return bool.Parse(Config.ReadSetting("Enabled_" + Task, true.ToString())); + } + else + { + return true; + } + } + set + { + if (_UserManageable == true) + { + Config.SetSetting("Enabled_" + Task, value.ToString()); + } + } + } + private bool _UserManageable; + public bool UserManageable => _UserManageable; public int Interval { get { - return int.Parse(Config.ReadSetting("Interval_" + Task, DefaultInterval.ToString())); + return int.Parse(Config.ReadSetting("Interval_" + Task, DefaultInterval.ToString())); } } public int DefaultInterval { get; set; } - public int MinimumAllowedValue { get; set; } + public int MinimumAllowedInterval { get; set; } + public List AllowedDays + { + get + { + string jsonDefaultAllowedDays = Newtonsoft.Json.JsonConvert.SerializeObject(DefaultAllowedDays); + return Newtonsoft.Json.JsonConvert.DeserializeObject>(Config.ReadSetting("AllowedDays_" + Task, jsonDefaultAllowedDays)); + } + } + public int AllowedStartHours + { + get + { + return int.Parse(Config.ReadSetting("AllowedStartHours_" + Task, DefaultAllowedStartHours.ToString())); + } + } + public int AllowedStartMinutes + { + get + { + return int.Parse(Config.ReadSetting("AllowedStartMinutes_" + Task, DefaultAllowedStartMinutes.ToString())); + } + } + public int AllowedEndHours + { + get + { + return int.Parse(Config.ReadSetting("AllowedEndHours_" + Task, DefaultAllowedEndHours.ToString())); + } + } + public int AllowedEndMinutes + { + get + { + return int.Parse(Config.ReadSetting("AllowedEndMinutes_" + Task, DefaultAllowedEndMinutes.ToString())); + } + } + public List DefaultAllowedDays { get; set; } + public int DefaultAllowedStartHours { get; set; } + public int DefaultAllowedStartMinutes { get; set; } + public int DefaultAllowedEndHours { get; set; } + public int DefaultAllowedEndMinutes { get; set; } + private List _Blocks = new List(); + public List Blocks + { + get + { + if (_Blocks.Contains(ProcessQueue.QueueItemType.All)) + { + List blockList = new List(); + List skipBlockItems = new List{ + ProcessQueue.QueueItemType.All, + ProcessQueue.QueueItemType.NotConfigured, + this.TaskEnum + }; + foreach (ProcessQueue.QueueItemType blockType in Enum.GetValues(typeof(ProcessQueue.QueueItemType))) + { + if (!skipBlockItems.Contains(blockType)) + { + blockList.Add(blockType); + } + } + return blockList; + } + else + { + return _Blocks; + } + } + } + public List BlockedBy + { + get + { + List blockedBy = new List(); + + List backgroundTaskItems = new List(); + foreach (ProcessQueue.QueueItemType blockType in Enum.GetValues(typeof(ProcessQueue.QueueItemType))) + { + if (blockType != this.TaskEnum) + { + BackgroundTaskItem taskItem = new BackgroundTaskItem(blockType); + if (taskItem.Blocks.Contains(this.TaskEnum)) + { + if (!blockedBy.Contains(blockType)) + { + blockedBy.Add(blockType); + } + } + } + } + + return blockedBy; + } + } + } + + public class BackgroundTaskSettingsItem + { + public string Task { get; set; } + public bool Enabled { get; set; } + public int Interval { get; set; } + public List AllowedDays { get; set; } + public int AllowedStartHours { get; set; } + public int AllowedStartMinutes { get; set; } + public int AllowedEndHours { get; set; } + public int AllowedEndMinutes { get; set; } } public class SystemSettingsModel diff --git a/gaseous-server/Controllers/V1.1/FirstSetupController.cs b/gaseous-server/Controllers/V1.1/FirstSetupController.cs index edf7a7c..a8ae898 100644 --- a/gaseous-server/Controllers/V1.1/FirstSetupController.cs +++ b/gaseous-server/Controllers/V1.1/FirstSetupController.cs @@ -40,7 +40,7 @@ namespace gaseous_server.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task CreateAdminAccount(Authentication.RegisterViewModel model) { - if (Config.ReadSetting("FirstRunStatus", "0") == "0") + if (Config.ReadSetting("FirstRunStatus", "0") == "0") { if (ModelState.IsValid) { @@ -68,7 +68,7 @@ namespace gaseous_server.Controllers await _signInManager.SignInAsync(user, isPersistent: true); Logging.Log(Logging.LogType.Information, "First Run", "Setting first run state to 1"); - Config.SetSetting("FirstRunStatus", "1"); + Config.SetSetting("FirstRunStatus", "1"); return Ok(result); } diff --git a/gaseous-server/Controllers/V1.1/StateManagerController.cs b/gaseous-server/Controllers/V1.1/StateManagerController.cs index 33bb15b..37398e0 100644 --- a/gaseous-server/Controllers/V1.1/StateManagerController.cs +++ b/gaseous-server/Controllers/V1.1/StateManagerController.cs @@ -36,9 +36,11 @@ namespace gaseous_server.Controllers.v1_1 public async Task SaveStateAsync(long RomId, UploadStateModel uploadState, bool IsMediaGroup = false) { var user = await _userManager.GetUserAsync(User); + + byte[] CompressedState = Common.Compress(uploadState.StateByteArray); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "INSERT INTO GameState (UserId, RomId, IsMediaGroup, StateDateTime, Name, Screenshot, State) VALUES (@userid, @romid, @ismediagroup, @statedatetime, @name, @screenshot, @state); SELECT LAST_INSERT_ID();"; + string sql = "INSERT INTO GameState (UserId, RomId, IsMediaGroup, StateDateTime, Name, Screenshot, State, Zipped) VALUES (@userid, @romid, @ismediagroup, @statedatetime, @name, @screenshot, @state, @zipped); SELECT LAST_INSERT_ID();"; Dictionary dbDict = new Dictionary { { "userid", user.Id }, @@ -47,10 +49,20 @@ namespace gaseous_server.Controllers.v1_1 { "statedatetime", DateTime.UtcNow }, { "name", "" }, { "screenshot", uploadState.ScreenshotByteArray }, - { "state", uploadState.StateByteArray } + { "state", CompressedState }, + { "zipped", true } }; DataTable data = db.ExecuteCMD(sql, dbDict); + if (IsMediaGroup == false) + { + Logging.Log(Logging.LogType.Information, "Save State", "Saved state for rom id " + RomId + ". State size: " + uploadState.StateByteArrayBase64.Length + " Compressed size: " + CompressedState.Length); + } + else + { + Logging.Log(Logging.LogType.Information, "Save State", "Saved state for media group id " + RomId + ". State size: " + uploadState.StateByteArrayBase64.Length + " Compressed size: " + CompressedState.Length); + } + return Ok(await GetStateAsync(RomId, (long)(ulong)data.Rows[0][0], IsMediaGroup)); } @@ -224,7 +236,7 @@ namespace gaseous_server.Controllers.v1_1 { var user = await _userManager.GetUserAsync(User); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "SELECT State FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;"; + string sql = "SELECT Zipped, State FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;"; Dictionary dbDict = new Dictionary { { "id", StateId }, @@ -242,7 +254,15 @@ namespace gaseous_server.Controllers.v1_1 else { string filename = "savestate.state"; - byte[] bytes = (byte[])data.Rows[0][0]; + byte[] bytes; + if ((bool)data.Rows[0]["Zipped"] == false) + { + bytes = (byte[])data.Rows[0]["State"]; + } + else + { + bytes = Common.Decompress((byte[])data.Rows[0]["State"]); + } string contentType = "application/octet-stream"; var cd = new System.Net.Mime.ContentDisposition diff --git a/gaseous-server/ProcessQueue.cs b/gaseous-server/ProcessQueue.cs index 5128200..82807e0 100644 --- a/gaseous-server/ProcessQueue.cs +++ b/gaseous-server/ProcessQueue.cs @@ -2,6 +2,8 @@ using System.ComponentModel.Design.Serialization; using System.Data; using gaseous_server.Classes; +using gaseous_server.Controllers; +using Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Manage.Internal; using NuGet.Common; using NuGet.Packaging; @@ -13,25 +15,63 @@ namespace gaseous_server public class QueueItem { + public QueueItem(QueueItemType ItemType, bool AllowManualStart = true, bool RemoveWhenStopped = false) + { + _ItemType = ItemType; + _ItemState = QueueItemState.NeverStarted; + _LastRunTime = Config.ReadSetting("LastRun_" + _ItemType.ToString(), DateTime.UtcNow.AddMinutes(-5)); + _AllowManualStart = AllowManualStart; + _RemoveWhenStopped = RemoveWhenStopped; + + // load queueitem configuration + BackgroundTaskItem defaultItem = new BackgroundTaskItem(ItemType); + Enabled(defaultItem.Enabled); + _Interval = defaultItem.Interval; + _AllowedDays = defaultItem.AllowedDays; + AllowedStartHours = defaultItem.AllowedStartHours; + AllowedStartMinutes = defaultItem.AllowedStartMinutes; + AllowedEndHours = defaultItem.AllowedEndHours; + AllowedEndMinutes = defaultItem.AllowedEndMinutes; + _Blocks = defaultItem.Blocks; + } + public QueueItem(QueueItemType ItemType, int ExecutionInterval, bool AllowManualStart = true, bool RemoveWhenStopped = false) { _ItemType = ItemType; _ItemState = QueueItemState.NeverStarted; - _LastRunTime = DateTime.Parse(Config.ReadSetting("LastRun_" + _ItemType.ToString(), DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ssZ"))).AddMinutes(-5); + _LastRunTime = Config.ReadSetting("LastRun_" + _ItemType.ToString(), DateTime.UtcNow.AddMinutes(-5)); _Interval = ExecutionInterval; _AllowManualStart = AllowManualStart; _RemoveWhenStopped = RemoveWhenStopped; + + // load timing defaults + BackgroundTaskItem defaultItem = new BackgroundTaskItem(ItemType); + Enabled(defaultItem.Enabled); + _AllowedDays = defaultItem.AllowedDays; + AllowedStartHours = defaultItem.AllowedStartHours; + AllowedStartMinutes = defaultItem.AllowedStartMinutes; + AllowedEndHours = defaultItem.AllowedEndHours; + AllowedEndMinutes = defaultItem.AllowedEndMinutes; } public QueueItem(QueueItemType ItemType, int ExecutionInterval, List Blocks, bool AllowManualStart = true, bool RemoveWhenStopped = false) { _ItemType = ItemType; _ItemState = QueueItemState.NeverStarted; - _LastRunTime = DateTime.Parse(Config.ReadSetting("LastRun_" + _ItemType.ToString(), DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ssZ"))).AddMinutes(-5); + _LastRunTime = Config.ReadSetting("LastRun_" + _ItemType.ToString(), DateTime.UtcNow.AddMinutes(-5)); _Interval = ExecutionInterval; _AllowManualStart = AllowManualStart; _RemoveWhenStopped = RemoveWhenStopped; _Blocks = Blocks; + + // load timing defaults + BackgroundTaskItem defaultItem = new BackgroundTaskItem(ItemType); + Enabled(defaultItem.Enabled); + _AllowedDays = defaultItem.AllowedDays; + AllowedStartHours = defaultItem.AllowedStartHours; + AllowedStartMinutes = defaultItem.AllowedStartMinutes; + AllowedEndHours = defaultItem.AllowedEndHours; + AllowedEndMinutes = defaultItem.AllowedEndMinutes; } private QueueItemType _ItemType = QueueItemType.NotConfigured; @@ -42,13 +82,15 @@ namespace gaseous_server { get { - return DateTime.Parse(Config.ReadSetting("LastRun_" + _ItemType.ToString(), DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ssZ"))); + // return DateTime.Parse(Config.ReadSetting("LastRun_" + _ItemType.ToString(), DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ssZ"))); + return Config.ReadSetting("LastRun_" + _ItemType.ToString(), DateTime.UtcNow); } set { if (_SaveLastRunTime == true) { - Config.SetSetting("LastRun_" + _ItemType.ToString(), value.ToString("yyyy-MM-ddThh:mm:ssZ")); + //Config.SetSetting("LastRun_" + _ItemType.ToString(), value.ToString("yyyy-MM-ddThh:mm:ssZ")); + Config.SetSetting("LastRun_" + _ItemType.ToString(), value); } } } @@ -61,8 +103,33 @@ namespace gaseous_server private bool _RemoveWhenStopped = false; private bool _IsBlocked = false; private string _CorrelationId = ""; + private List _AllowedDays = new List + { + DayOfWeek.Sunday, + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, + DayOfWeek.Saturday + }; private List _Blocks = new List(); + public List AllowedDays + { + get + { + return _AllowedDays; + } + set + { + _AllowedDays = value; + } + } + public int AllowedStartHours { get; set; } = 0; + public int AllowedStartMinutes { get; set; } = 0; + public int AllowedEndHours { get; set; } = 23; + public int AllowedEndMinutes { get; set; } = 59; public QueueItemType ItemType => _ItemType; public QueueItemState ItemState => _ItemState; public DateTime LastRunTime => _LastRunTime; @@ -72,9 +139,56 @@ namespace gaseous_server { get { - return LastRunTime.AddMinutes(Interval); + // next run time + DateTime tempNextRun = LastRunTime.ToLocalTime().AddMinutes(Interval); + // if (tempNextRun < DateTime.Now) + // { + // tempNextRun = DateTime.Now; + // } + DayOfWeek nextWeekDay = tempNextRun.DayOfWeek; + + // create local start and end times + DateTime tempStartTime = new DateTime(tempNextRun.Year, tempNextRun.Month, tempNextRun.Day, AllowedStartHours, AllowedStartMinutes, 0, DateTimeKind.Local); + DateTime tempEndTime = new DateTime(tempNextRun.Year, tempNextRun.Month, tempNextRun.Day, AllowedEndHours, AllowedEndMinutes, 0, DateTimeKind.Local); + + // bump the next run time to the next allowed day and hour range + if (AllowedDays.Contains(nextWeekDay)) + { + // next run day is allowed, nothing to do + } + else + { + // keep bumping the day forward until the a weekday is found + do + { + tempNextRun = tempNextRun.AddDays(1); + nextWeekDay = tempNextRun.DayOfWeek; + } + while (!AllowedDays.Contains(nextWeekDay)); + + // update windows + tempStartTime = new DateTime(tempNextRun.Year, tempNextRun.Month, tempNextRun.Day, AllowedStartHours, AllowedStartMinutes, 0, DateTimeKind.Local); + tempEndTime = new DateTime(tempNextRun.Year, tempNextRun.Month, tempNextRun.Day, AllowedEndHours, AllowedEndMinutes, 0, DateTimeKind.Local); + } + + // are the hours in the right range + TimeSpan spanNextRun = tempNextRun.TimeOfDay; + if (LastRunTime.ToLocalTime().AddMinutes(Interval) < tempStartTime) + { + return tempStartTime.ToUniversalTime(); + } + else if (spanNextRun >= tempStartTime.TimeOfDay && spanNextRun <= tempEndTime.TimeOfDay) + { + // all good - return nextRun + return tempNextRun.ToUniversalTime(); + } + else + { + return tempStartTime.ToUniversalTime(); + } } } + public int Interval { get @@ -233,12 +347,25 @@ namespace gaseous_server DatabaseMigration.UpgradeScriptBackgroundTasks(); break; - case QueueItemType.Maintainer: - Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Maintenance"); + case QueueItemType.DailyMaintainer: + Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Daily Maintenance"); Classes.Maintenance maintenance = new Maintenance{ CallingQueueItem = this }; - maintenance.RunMaintenance(); + maintenance.RunDailyMaintenance(); + + _SaveLastRunTime = true; + + break; + + case QueueItemType.WeeklyMaintainer: + Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Weekly Maintenance"); + Classes.Maintenance weeklyMaintenance = new Maintenance{ + CallingQueueItem = this + }; + weeklyMaintenance.RunWeeklyMaintenance(); + + _SaveLastRunTime = true; break; case QueueItemType.TempCleanup: @@ -277,7 +404,14 @@ namespace gaseous_server } _ForceExecute = false; - _ItemState = QueueItemState.Stopped; + if (_DisableWhenComplete == false) + { + _ItemState = QueueItemState.Stopped; + } + else + { + _ItemState = QueueItemState.Disabled; + } _LastFinishTime = DateTime.UtcNow; _LastRunDuration = Math.Round((DateTime.UtcNow - _LastRunTime).TotalSeconds, 2); @@ -296,6 +430,26 @@ namespace gaseous_server _IsBlocked = BlockState; } + private bool _DisableWhenComplete = false; + public void Enabled(bool Enabled) + { + _DisableWhenComplete = !Enabled; + if (Enabled == true) + { + if (_ItemState == QueueItemState.Disabled) + { + _ItemState = QueueItemState.Stopped; + } + } + else + { + if (_ItemState == QueueItemState.Stopped || _ItemState == QueueItemState.NeverStarted) + { + _ItemState = QueueItemState.Disabled; + } + } + } + public HasErrorsItem HasErrors { get @@ -420,9 +574,14 @@ namespace gaseous_server BackgroundDatabaseUpgrade, /// - /// Performs a clean up of old files, and optimises the database + /// Performs a clean up of old files, and purge old logs /// - Maintainer, + DailyMaintainer, + + /// + /// Performs more intensive cleanups and optimises the database + /// + WeeklyMaintainer, /// /// Cleans up marked paths in the temporary directory diff --git a/gaseous-server/Program.cs b/gaseous-server/Program.cs index 57026e7..62b13e3 100644 --- a/gaseous-server/Program.cs +++ b/gaseous-server/Program.cs @@ -55,15 +55,6 @@ Communications.MetadataSource = Config.MetadataConfiguration.MetadataSource; // set up hasheous client HasheousClient.WebApp.HttpHelper.BaseUri = Config.MetadataConfiguration.HasheousHost; -// set initial values -Guid APIKey = Guid.NewGuid(); -if (Config.ReadSetting("API Key", "Test API Key") == "Test API Key") -{ - // it's a new api key save it - Logging.Log(Logging.LogType.Information, "Startup", "Setting initial API key"); - Config.SetSetting("API Key", APIKey.ToString()); -} - // clean up storage if (Directory.Exists(Config.LibraryConfiguration.LibraryTempDirectory)) { @@ -421,69 +412,38 @@ var platformMap = PlatformMapping.PlatformMap; // add background tasks ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( - ProcessQueue.QueueItemType.SignatureIngestor, - int.Parse(Config.ReadSetting("Interval_SignatureIngestor", "60")) - ) + ProcessQueue.QueueItemType.SignatureIngestor) ); ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( - ProcessQueue.QueueItemType.TitleIngestor, - int.Parse(Config.ReadSetting("Interval_TitleIngestor", "1")), - new List - { - ProcessQueue.QueueItemType.OrganiseLibrary, - ProcessQueue.QueueItemType.LibraryScan - }) + ProcessQueue.QueueItemType.TitleIngestor) ); ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( - ProcessQueue.QueueItemType.MetadataRefresh, - int.Parse(Config.ReadSetting("Interval_MetadataRefresh", "360")) - ) + ProcessQueue.QueueItemType.MetadataRefresh) ); ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( - ProcessQueue.QueueItemType.OrganiseLibrary, - int.Parse(Config.ReadSetting("Interval_OrganiseLibrary", "1440")), - new List - { - ProcessQueue.QueueItemType.LibraryScan, - ProcessQueue.QueueItemType.TitleIngestor, - ProcessQueue.QueueItemType.Rematcher - }) + ProcessQueue.QueueItemType.OrganiseLibrary) ); ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( - ProcessQueue.QueueItemType.LibraryScan, - int.Parse(Config.ReadSetting("Interval_LibraryScan", "1440")), - new List - { - ProcessQueue.QueueItemType.OrganiseLibrary, - ProcessQueue.QueueItemType.Rematcher - }) + ProcessQueue.QueueItemType.LibraryScan) ); ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( - ProcessQueue.QueueItemType.Rematcher, - int.Parse(Config.ReadSetting("Interval_Rematcher", "1440")), - new List - { - ProcessQueue.QueueItemType.OrganiseLibrary, - ProcessQueue.QueueItemType.LibraryScan - }) - ); -ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( - ProcessQueue.QueueItemType.Maintainer, - int.Parse(Config.ReadSetting("Interval_Maintainer", "10080")), - new List - { - ProcessQueue.QueueItemType.All - }) + ProcessQueue.QueueItemType.Rematcher) ); -ProcessQueue.QueueItem tempCleanup = new ProcessQueue.QueueItem( - ProcessQueue.QueueItemType.TempCleanup, - 1, - new List(), - false, - false +// maintenance tasks +ProcessQueue.QueueItem dailyMaintenance = new ProcessQueue.QueueItem( + ProcessQueue.QueueItemType.DailyMaintainer + ); +ProcessQueue.QueueItems.Add(dailyMaintenance); + +ProcessQueue.QueueItem weeklyMaintenance = new ProcessQueue.QueueItem( + ProcessQueue.QueueItemType.WeeklyMaintainer + ); +ProcessQueue.QueueItems.Add(weeklyMaintenance); + +ProcessQueue.QueueItem tempCleanup = new ProcessQueue.QueueItem( + ProcessQueue.QueueItemType.TempCleanup ); -tempCleanup.ForceExecute(); ProcessQueue.QueueItems.Add(tempCleanup); Logging.WriteToDiskOnly = false; diff --git a/gaseous-server/Support/Database/MySQL/gaseous-1016.sql b/gaseous-server/Support/Database/MySQL/gaseous-1016.sql new file mode 100644 index 0000000..3a94880 --- /dev/null +++ b/gaseous-server/Support/Database/MySQL/gaseous-1016.sql @@ -0,0 +1,6 @@ +ALTER TABLE `Settings` +ADD COLUMN `ValueType` INT NULL DEFAULT 0 AFTER `Setting`, +ADD COLUMN `ValueDate` DATETIME NULL DEFAULT NULL AFTER `Value`; + +ALTER TABLE `GameState` +ADD COLUMN `Zipped` BOOLEAN NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/gaseous-server/Timer.cs b/gaseous-server/Timer.cs index d9cf2a6..fd91807 100644 --- a/gaseous-server/Timer.cs +++ b/gaseous-server/Timer.cs @@ -20,8 +20,8 @@ namespace gaseous_server //_logger.LogInformation("Timed Hosted Service running."); Logging.Log(Logging.LogType.Debug, "Background", "Starting background task monitor"); - _timer = new Timer(DoWork, null, TimeSpan.Zero, - TimeSpan.FromSeconds(5)); + _timer = new Timer(DoWork, null, TimeSpan.FromSeconds(30), + TimeSpan.FromSeconds(30)); return Task.CompletedTask; } @@ -36,20 +36,23 @@ namespace gaseous_server List ActiveList = new List(); ActiveList.AddRange(ProcessQueue.QueueItems); foreach (ProcessQueue.QueueItem qi in ActiveList) { - if (CheckIfProcessIsBlockedByOthers(qi) == false) { - qi.BlockedState(false); - if (DateTime.UtcNow > qi.NextRunTime || qi.Force == true) - { - qi.Execute(); - if (qi.RemoveWhenStopped == true && qi.ItemState == ProcessQueue.QueueItemState.Stopped) + if (qi.ItemState != ProcessQueue.QueueItemState.Disabled) + { + if (CheckIfProcessIsBlockedByOthers(qi) == false) { + qi.BlockedState(false); + if (DateTime.UtcNow > qi.NextRunTime || qi.Force == true) { - ProcessQueue.QueueItems.Remove(qi); + qi.Execute(); + if (qi.RemoveWhenStopped == true && qi.ItemState == ProcessQueue.QueueItemState.Stopped) + { + ProcessQueue.QueueItems.Remove(qi); + } } } - } - else - { - qi.BlockedState(true); + else + { + qi.BlockedState(true); + } } } } diff --git a/gaseous-server/gaseous-server.csproj b/gaseous-server/gaseous-server.csproj index 0a82578..f07ff59 100644 --- a/gaseous-server/gaseous-server.csproj +++ b/gaseous-server/gaseous-server.csproj @@ -60,6 +60,7 @@ + @@ -97,5 +98,6 @@ + diff --git a/gaseous-server/wwwroot/emulators/EmulatorJS.html b/gaseous-server/wwwroot/emulators/EmulatorJS.html index 8165dd7..e4ef871 100644 --- a/gaseous-server/wwwroot/emulators/EmulatorJS.html +++ b/gaseous-server/wwwroot/emulators/EmulatorJS.html @@ -29,6 +29,7 @@ EJS_pathtodata = '/emulators/EmulatorJS/data/'; EJS_DEBUG_XX = false; + console.log("Debug enabled: " + EJS_DEBUG_XX); EJS_backgroundImage = emuBackground; EJS_backgroundBlur = true; diff --git a/gaseous-server/wwwroot/pages/dialogs/settingsuseredit.html b/gaseous-server/wwwroot/pages/dialogs/settingsuseredit.html index f69f44a..401335c 100644 --- a/gaseous-server/wwwroot/pages/dialogs/settingsuseredit.html +++ b/gaseous-server/wwwroot/pages/dialogs/settingsuseredit.html @@ -268,7 +268,7 @@ if (newPassword.length > 0) { if (newPassword == conPassword) { // check if password meets requirements - if (newPassword.length > 10) { + if (newPassword.length >= 10) { errorLabel.innerHTML = ""; submitButton.removeAttribute('disabled'); return true; diff --git a/gaseous-server/wwwroot/pages/dialogs/settingsusernew.html b/gaseous-server/wwwroot/pages/dialogs/settingsusernew.html index 13f1644..1c04efa 100644 --- a/gaseous-server/wwwroot/pages/dialogs/settingsusernew.html +++ b/gaseous-server/wwwroot/pages/dialogs/settingsusernew.html @@ -49,7 +49,7 @@ if (userNameVal.includes("@")) { if (newPassword == conPassword) { // check if password meets requirements - if (newPassword.length > 10) { + if (newPassword.length >= 10) { errorLabel.innerHTML = ""; submitButton.removeAttribute('disabled'); } else { diff --git a/gaseous-server/wwwroot/pages/dialogs/userprofile.html b/gaseous-server/wwwroot/pages/dialogs/userprofile.html index b853127..0ede6b3 100644 --- a/gaseous-server/wwwroot/pages/dialogs/userprofile.html +++ b/gaseous-server/wwwroot/pages/dialogs/userprofile.html @@ -285,7 +285,7 @@ } else { if (newPassword == conPassword) { // check if password meets requirements - if (newPassword.length > 10) { + if (newPassword.length >= 10) { errorLabel.innerHTML = ""; submitButton.removeAttribute('disabled'); } else { diff --git a/gaseous-server/wwwroot/pages/first.html b/gaseous-server/wwwroot/pages/first.html index 2a357b9..dededdb 100644 --- a/gaseous-server/wwwroot/pages/first.html +++ b/gaseous-server/wwwroot/pages/first.html @@ -117,7 +117,7 @@ } else { if (newPassword == conPassword) { // check if password meets requirements - if (newPassword.length > 10) { + if (newPassword.length >= 10) { errorLabel.innerHTML = " "; submitButton.removeAttribute('disabled'); } else { diff --git a/gaseous-server/wwwroot/pages/settings/settings.html b/gaseous-server/wwwroot/pages/settings/settings.html index f7967b3..4d42d63 100644 --- a/gaseous-server/wwwroot/pages/settings/settings.html +++ b/gaseous-server/wwwroot/pages/settings/settings.html @@ -11,7 +11,6 @@

Advanced Settings

Warning Do not modify the below settings unless you know what you're doing.

Background Task Timers

-

All intervals are in minutes.

@@ -104,44 +103,241 @@ function getBackgroundTaskTimers() { ajaxCall( - '/api/v1/System/Settings/BackgroundTasks/Intervals', + '/api/v1/System/Settings/BackgroundTasks/Configuration', 'GET', function(result) { var targetTable = document.getElementById('settings_tasktimers'); targetTable.innerHTML = ''; - targetTable.appendChild( - createTableRow(true, ['Background Task', 'Timer Interval', 'Default Interval', 'Minimum Allowed Interval']) - ); - for (const [key, value] of Object.entries(result)) { - var newTableRow = createTableRow( + var newTableRowBody = document.createElement('tbody'); + newTableRowBody.className = 'romrow'; + + var enabledString = ""; + if (value.enabled == true) { + enabledString = 'checked="checked"'; + } + + var newTableIntervalRow = createTableRow( false, [ GetTaskFriendlyName(value.task), - '', - value.defaultInterval, - value.minimumAllowedValue + 'Enabled', + '', ], - 'romrow', + '', 'romcell' ); - targetTable.appendChild(newTableRow); + newTableRowBody.appendChild(newTableIntervalRow); + + var newTableRow = createTableRow( + false, + [ + '', + 'Minimum Interval (Minutes):', + '' + ], + '', + 'romcell' + ); + newTableRowBody.appendChild(newTableRow); + + // allowed time periods row + var newTableRowTime = document.createElement('tr'); + var rowTimeSpace = document.createElement('td'); + newTableRowTime.appendChild(rowTimeSpace); + + var rowTimeContentTitle = document.createElement('td'); + rowTimeContentTitle.className = 'romcell'; + rowTimeContentTitle.innerHTML = "Allowed Days:"; + newTableRowTime.appendChild(rowTimeContentTitle); + + var rowTimeContent = document.createElement('td'); + // rowTimeContent.setAttribute('colspan', 2); + rowTimeContent.className = 'romcell'; + var daySelector = document.createElement('select'); + daySelector.id = 'settings_alloweddays_' + value.task; + daySelector.name = 'settings_alloweddays'; + daySelector.multiple = 'multiple'; + daySelector.setAttribute('data-default', value.defaultAllowedDays.join(",")); + daySelector.style.width = '95%'; + var days = [ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" ]; + for (var d = 0; d < days.length; d++) { + var dayOpt = document.createElement('option'); + dayOpt.value = days[d]; + dayOpt.innerHTML = days[d]; + if (value.allowedDays.includes(days[d])) { + dayOpt.selected = 'selected'; + } + daySelector.appendChild(dayOpt); + } + rowTimeContent.appendChild(daySelector); + $(daySelector).select2({ + tags: false + }); + newTableRowTime.appendChild(rowTimeContent); + + newTableRowBody.appendChild(newTableRowTime); + + // add start and end times + var newTableRowClock = document.createElement('tr'); + var rowClockSpace = document.createElement('td'); + newTableRowClock.appendChild(rowClockSpace); + + var rowClockContentTitle = document.createElement('td'); + rowClockContentTitle.className = 'romcell'; + rowClockContentTitle.innerHTML = "Time Range:"; + newTableRowClock.appendChild(rowClockContentTitle); + + var rowClockContent = document.createElement('td'); + rowClockContent.className = 'romcell'; + // rowClockContent.setAttribute('colspan', 2); + + rowClockContent.appendChild(generateTimeDropDowns(value.task, 'Start', value.defaultAllowedStartHours, value.defaultAllowedStartMinutes, value.allowedStartHours, value.allowedStartMinutes)); + + rowClockContentSeparator = document.createElement('span'); + rowClockContentSeparator.innerHTML = ' - '; + rowClockContent.appendChild(rowClockContentSeparator); + + rowClockContent.appendChild(generateTimeDropDowns(value.task, 'End', value.defaultAllowedEndHours, value.defaultAllowedEndMinutes, value.allowedEndHours, value.allowedEndMinutes)); + + newTableRowClock.appendChild(rowClockContent); + + newTableRowBody.appendChild(newTableRowClock); + + // blocks tasks + var newTableRowBlocks = document.createElement('tr'); + var rowBlocksSpace = document.createElement('td'); + newTableRowBlocks.appendChild(rowBlocksSpace); + + var rowBlocksContentTitle = document.createElement('td'); + rowBlocksContentTitle.className = 'romcell'; + rowBlocksContentTitle.innerHTML = "Blocks:"; + newTableRowBlocks.appendChild(rowBlocksContentTitle); + + var rowBlocksContent = document.createElement('td'); + rowBlocksContent.className = 'romcell'; + // rowBlocksContent.setAttribute('colspan', 2); + var blocksString = ""; + for (var i = 0; i < value.blocks.length; i++) { + if (blocksString.length > 0) { blocksString += ", "; } + blocksString += GetTaskFriendlyName(value.blocks[i]); + } + if (blocksString.length == 0) { blocksString = 'None'; } + rowBlocksContent.innerHTML = blocksString; + newTableRowBlocks.appendChild(rowBlocksContent); + + newTableRowBody.appendChild(newTableRowBlocks); + + // blocked by tasks + var newTableRowBlockedBy = document.createElement('tr'); + var rowBlockedBySpace = document.createElement('td'); + newTableRowBlockedBy.appendChild(rowBlockedBySpace); + + var rowBlockedByContentTitle = document.createElement('td'); + rowBlockedByContentTitle.className = 'romcell'; + rowBlockedByContentTitle.innerHTML = "Blocked By:"; + newTableRowBlockedBy.appendChild(rowBlockedByContentTitle); + + var rowBlockedByContent = document.createElement('td'); + rowBlockedByContent.className = 'romcell'; + // rowBlockedByContent.setAttribute('colspan', 2); + var BlockedByString = ""; + for (var i = 0; i < value.blockedBy.length; i++) { + if (BlockedByString.length > 0) { BlockedByString += ", "; } + BlockedByString += GetTaskFriendlyName(value.blockedBy[i]); + } + if (BlockedByString.length == 0) { BlockedByString = 'None'; } + rowBlockedByContent.innerHTML = BlockedByString; + newTableRowBlockedBy.appendChild(rowBlockedByContent); + + newTableRowBody.appendChild(newTableRowBlockedBy); + + // complete row + targetTable.appendChild(newTableRowBody); } } ); } + function generateTimeDropDowns(taskName, rangeName, defaultHour, defaultMinute, valueHour, valueMinute) { + var container = document.createElement('div'); + container.style.display = 'inline'; + + var elementName = 'settings_tasktimers_time'; + + var hourSelector = document.createElement('input'); + hourSelector.id = 'settings_tasktimers_' + taskName + '_' + rangeName + '_Hour'; + hourSelector.name = elementName; + hourSelector.setAttribute('data-name', taskName); + hourSelector.setAttribute('type', 'number'); + hourSelector.setAttribute('min', '0'); + hourSelector.setAttribute('max', '23'); + hourSelector.setAttribute('placeholder', defaultHour); + hourSelector.value = valueHour; + container.appendChild(hourSelector); + + var separator = document.createElement('span'); + separator.innerHTML = " : "; + container.appendChild(separator); + + var minSelector = document.createElement('input'); + minSelector.id = 'settings_tasktimers_' + taskName + '_' + rangeName + '_Minute'; + minSelector.name = elementName; + minSelector.setAttribute('type', 'number'); + minSelector.setAttribute('min', '0'); + minSelector.setAttribute('max', '59'); + minSelector.setAttribute('placeholder', defaultMinute); + minSelector.value = valueMinute; + container.appendChild(minSelector); + + return container; + } + function saveTaskTimers() { var timerValues = document.getElementsByName('settings_tasktimers_values'); - var model = {}; + var model = []; for (var i = 0; i < timerValues.length; i++) { - model[timerValues[i].getAttribute('data-name')] = timerValues[i].value; + var taskName = timerValues[i].getAttribute('data-name'); + var taskEnabled = document.getElementById('settings_enabled_' + taskName).checked; + var taskIntervalObj = document.getElementById('settings_tasktimers_' + taskName); + var taskInterval = function() { if (taskIntervalObj.value) { return taskIntervalObj.value; } else { return taskIntervalObj.getAttribute('placeholder'); } }; + var taskDaysRaw = $('#settings_alloweddays_' + taskName).select2('data'); + var taskDays = []; + if (taskDaysRaw.length > 0) { + for (var d = 0; d < taskDaysRaw.length; d++) { + taskDays.push(taskDaysRaw[d].id); + } + } else { + taskDays.push("Monday"); + } + var taskStartHourObj = document.getElementById('settings_tasktimers_' + taskName + '_Start_Hour'); + var taskStartMinuteObj = document.getElementById('settings_tasktimers_' + taskName + '_Start_Minute'); + var taskEndHourObj = document.getElementById('settings_tasktimers_' + taskName + '_End_Hour'); + var taskEndMinuteObj = document.getElementById('settings_tasktimers_' + taskName + '_End_Minute'); + + var taskStartHour = function() { if (taskStartHourObj.value) { return taskStartHourObj.value; } else { return taskStartHourObj.getAttribute('placeholder'); } }; + var taskStartMinute = function() { if (taskStartMinuteObj.value) { return taskStartMinuteObj.value; } else { return taskStartMinuteObj.getAttribute('placeholder'); } }; + var taskEndHour = function() { if (taskEndHourObj.value) { return taskEndHourObj.value; } else { return taskEndHourObj.getAttribute('placeholder'); } }; + var taskEndMinute = function() { if (taskEndMinuteObj.value) { return taskEndMinuteObj.value; } else { return taskEndMinuteObj.getAttribute('placeholder'); } }; + + model.push( + { + "task": taskName, + "enabled": taskEnabled, + "interval": taskInterval(), + "allowedDays": taskDays, + "allowedStartHours": taskStartHour(), + "allowedStartMinutes": taskStartMinute(), + "allowedEndHours": taskEndHour(), + "allowedEndMinutes": taskEndMinute() + } + ); } ajaxCall( - '/api/v1/System/Settings/BackgroundTasks/Intervals', + '/api/v1/System/Settings/BackgroundTasks/Configuration', 'POST', function(result) { getBackgroundTaskTimers(); @@ -154,12 +350,32 @@ } function defaultTaskTimers() { + var timerValues = document.getElementsByName('settings_tasktimers_enabled'); + + for (var i = 0; i < timerValues.length; i++) { + timerValues[i].checked = true; + } + var timerValues = document.getElementsByName('settings_tasktimers_values'); for (var i = 0; i < timerValues.length; i++) { timerValues[i].value = timerValues[i].getAttribute('data-default'); } + var timerValues = document.getElementsByName('settings_alloweddays'); + + for (var i = 0; i < timerValues.length; i++) { + var defaultSelections = timerValues[i].getAttribute('data-default').split(','); + $(timerValues[i]).val(defaultSelections); + $(timerValues[i]).trigger('change'); + } + + var timerValues = document.getElementsByName('settings_tasktimers_time'); + + for (var i = 0; i < timerValues.length; i++) { + timerValues[i].value = timerValues[i].getAttribute('placeholder'); + } + saveTaskTimers(); } diff --git a/gaseous-server/wwwroot/scripts/main.js b/gaseous-server/wwwroot/scripts/main.js index 6271d79..f33d4a6 100644 --- a/gaseous-server/wwwroot/scripts/main.js +++ b/gaseous-server/wwwroot/scripts/main.js @@ -410,13 +410,25 @@ function GetTaskFriendlyName(TaskName, options) { case 'LibraryScan': return "Library scan"; case 'LibraryScanWorker': - return "Library scan worker: " + options.name; + if (options) { + return "Library scan worker: " + options.name; + } else { + return "Library scan worker"; + } case 'CollectionCompiler': - return "Compress collection id: " + options; + if (options) { + return "Compress collection id: " + options; + } else { + return "Compress collection"; + } case 'BackgroundDatabaseUpgrade': return "Background database upgrade"; case 'TempCleanup': return "Temporary directory cleanup"; + case 'DailyMaintainer': + return "Daily maintenance"; + case 'WeeklyMaintainer': + return "Weekly maintenance"; default: return TaskName; }