Files
raven/server/AyaNova/util/ServerGlobalOpsSettingsCache.cs
John Cardinal a9d5cab7e5 case 4682 - automatic backup runs at the chosen time on the server's clock all year
The backup time is stored as the UTC moment it meant on the day it was set
and was run at that UTC hour every day, so on servers with daylight saving it
ran an hour off the chosen time for part of the year. SetNextBackup now
recovers the clock time with the stored date's offset and runs it on the
server's local days. No migration. Backup manual page and changelog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 09:09:50 -07:00

98 lines
4.2 KiB
C#

using System;
using System.Linq;
using AyaNova.Models;
namespace AyaNova.Util
{
/// <summary>
/// Contains static mirror copy in memory of Operations related settings stored in db
/// that are accessed frequently by server
/// </summary>
internal static class ServerGlobalOpsSettingsCache
{
//BOOT flag, set during boot and
//is used to control generator from starting
internal static bool BOOTING { get; set; }
//False if db server is detected to be down
//is used to control generator from processing
internal static bool DBAVAILABLE { get; set; }
internal static GlobalOpsBackupSettings Backup { get; set; }
internal static GlobalOpsNotificationSettings Notify { get; set; }
internal static DateTime NextBackup { get; set; }
//VERSION CHECKING
//set by CoreJobVersionCheck once daily and kept here statically while server is up
internal static string LATEST_VERSION { get; set; }
internal static string CHANGE_LOG_URL { get; set; }
/// <summary>
/// Populate and / or create the settings
/// </summary>
internal static void Initialize(AyContext ct = null)
{
//set to current servers boot version so as to not trigger messaging at client on login until job has had a chance to set
LATEST_VERSION = Util.AyaNovaVersion.VersionString;
CHANGE_LOG_URL = "https://ayanova.com/docs/changelog/";//just something to default to, should never come up as it's not a different version at this point
//fetch or create as not provided (meaning this was called from Startup.cs)
Backup = ct.GlobalOpsBackupSettings.FirstOrDefault(z => z.Id == 1);
if (Backup == null)
{
Backup = new GlobalOpsBackupSettings();
ct.GlobalOpsBackupSettings.Add(Backup);
ct.SaveChanges();
}
NextBackup = FileUtil.MostRecentAutomatedBackupFileDate();
SetNextBackup();
Notify = ct.GlobalOpsNotificationSettings.FirstOrDefault(z => z.Id == 1);
if (Notify == null)
{
Notify = new GlobalOpsNotificationSettings();
ct.GlobalOpsNotificationSettings.Add(Notify);
ct.SaveChanges();
}
}
internal static void SetNextBackup()
{
DateTime utcNow = DateTime.UtcNow;
//case 4682: the backup time is a time on the server's clock; days are the server's local days
DateTime localToday = TimeZoneInfo.ConvertTimeFromUtc(utcNow, TimeZoneInfo.Local).Date;
//Has last backup run more than 24 hours ago?
if (NextBackup < utcNow.AddHours(-24))
{
//set it to today then
NextBackup = BackupTimeOnLocalDay(localToday);
//Make sure next backup is in the future
if (NextBackup < utcNow)
NextBackup = BackupTimeOnLocalDay(localToday.AddDays(1));
}
else
{
//less than 24 hours, set it to tomorrow
NextBackup = BackupTimeOnLocalDay(localToday.AddDays(1));
}
}
//case 4682: the backup time is stored as the UTC moment it meant on the day it was set, and was run at that UTC hour
//every day, so it moved an hour on the server's clock with each daylight saving change. The server's clock time is
//recovered with that day's offset and the backup is run at that clock time on the given local day (UTC returned)
private static DateTime BackupTimeOnLocalDay(DateTime localDay)
{
var chosen = TimeZoneInfo.ConvertTimeFromUtc(DateTime.SpecifyKind(Backup.BackupTime, DateTimeKind.Utc), TimeZoneInfo.Local);
var local = DateTime.SpecifyKind(localDay.Date + new TimeSpan(chosen.Hour, chosen.Minute, 0), DateTimeKind.Unspecified);
//a time skipped when the clocks go forward (e.g. 2:30 AM that night) runs an hour later
if (TimeZoneInfo.Local.IsInvalidTime(local))
local = local.AddHours(1);
return TimeZoneInfo.ConvertTimeToUtc(local, TimeZoneInfo.Local);
}
}//eoc
}//eons