diff --git a/NINA.WPF.Base/SkySurvey/CacheSkySurvey.cs b/NINA.WPF.Base/SkySurvey/CacheSkySurvey.cs index 9103deb41e..b57d6d268e 100644 --- a/NINA.WPF.Base/SkySurvey/CacheSkySurvey.cs +++ b/NINA.WPF.Base/SkySurvey/CacheSkySurvey.cs @@ -1,7 +1,7 @@ #region "copyright" /* - Copyright © 2016 - 2026 Stefan Berg and the N.I.N.A. contributors + Copyright � 2016 - 2026 Stefan Berg and the N.I.N.A. contributors This file is part of N.I.N.A. - Nighttime Imaging 'N' Astronomy. @@ -15,6 +15,7 @@ This Source Code Form is subject to the terms of the Mozilla Public using NINA.Astrometry; using NINA.Core.Utility; using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -27,12 +28,22 @@ This Source Code Form is subject to the terms of the Mozilla Public namespace NINA.WPF.Base.SkySurvey { public class CacheSkySurvey { + public const string Default = "Default"; + public readonly string framingAssistantCachePath; private string framingAssistantCachInfo; + private Dictionary cachesBySource; + private Dictionary cachePathsBySource; + private string activeSource = Default; + private string activeCachePath; + + public string ActiveCachePath => string.IsNullOrEmpty(activeCachePath) ? framingAssistantCachePath : activeCachePath; public CacheSkySurvey(string framingAssistantCachePath) { this.framingAssistantCachePath = framingAssistantCachePath; this.framingAssistantCachInfo = Path.Combine(framingAssistantCachePath, "CacheInfo.xml"); + this.cachesBySource = new Dictionary(); + this.cachePathsBySource = new Dictionary(); Initialize(); } @@ -79,12 +90,18 @@ public void Clear() { public void DeleteFromCache(XElement element) { - if(Cache != null && element != null) { + if(element != null) { + // Determine active cache document and paths + var doc = GetActiveCacheElement(); + var basePath = ActiveCachePath; + var infoPath = (activeSource == Default || string.IsNullOrEmpty(activeSource)) + ? framingAssistantCachInfo + : Path.Combine(ActiveCachePath, "CacheInfo.xml"); var fileNameAttribute = element.Attribute("FileName"); if(fileNameAttribute != null) { - var fullFileName = Path.Combine(framingAssistantCachePath, fileNameAttribute.Value); + var fullFileName = Path.Combine(basePath, fileNameAttribute.Value); var thumbnailBig = CacheImage.GetImagePathForThumbnail(fullFileName, CacheImage.BigThumbnailSize); var thumbnailMedium = CacheImage.GetImagePathForThumbnail(fullFileName, CacheImage.MediumThumbnailSize); var thumbnailSmall = CacheImage.GetImagePathForThumbnail(fullFileName, CacheImage.SmallThumbnailSize); @@ -101,11 +118,10 @@ public void DeleteFromCache(XElement element) { if (File.Exists(thumbnailSmall)) { try { File.Delete(thumbnailSmall); } catch { } } - } element.Remove(); - Cache.Save(framingAssistantCachInfo); + doc?.Save(infoPath); } } @@ -219,6 +235,7 @@ private string RestoreNameFromUniqueBracket(string originalImgFilePath) { /// public Task GetImage(string source, double ra, double dec, double rotation, double fov) { return Task.Run(() => { + // Online sources and file cache are stored in the default cache (root) var element = Cache .Elements("Image") @@ -230,7 +247,7 @@ public Task GetImage(string source, double ra, double dec, doubl .FirstOrDefault(); if (element != null) { - return Load(element); + return Load(element, framingAssistantCachePath); } return null; @@ -246,7 +263,7 @@ public Task GetImage(Guid id) { x => x.Attribute("Id").Value == id.ToString() ).FirstOrDefault(); if (element != null) { - return Load(element); + return Load(element, framingAssistantCachePath); } else { return null; } @@ -254,7 +271,11 @@ public Task GetImage(Guid id) { } private SkySurveyImage Load(XElement element) { - var img = LoadJpg(element.Attribute("FileName").Value); + return Load(element, framingAssistantCachePath); + } + + private SkySurveyImage Load(XElement element, string cachePath) { + var img = LoadJpg(element.Attribute("FileName").Value, cachePath); Guid id = Guid.Parse(element.Attribute("Id").Value); var fovW = double.Parse(element.Attribute("FoVW").Value, CultureInfo.InvariantCulture); var fovH = double.Parse(element.Attribute("FoVH").Value, CultureInfo.InvariantCulture); @@ -277,8 +298,12 @@ private SkySurveyImage Load(XElement element) { } private BitmapSource LoadJpg(string filename) { + return LoadJpg(filename, framingAssistantCachePath); + } + + private BitmapSource LoadJpg(string filename, string cachePath) { if (!Path.IsPathRooted(filename)) { - filename = Path.Combine(framingAssistantCachePath, filename); + filename = Path.Combine(cachePath, filename); } JpegBitmapDecoder JpgDec = new JpegBitmapDecoder(new Uri(filename), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); @@ -297,5 +322,91 @@ private BitmapSource ConvertTo96Dpi(BitmapSource image) { return image; } + + /// + /// Scans subdirectories for cache sources and returns list of available sources + /// + public List GetAvailableCacheSources() { + var sources = new List { Default }; + + try { + if (Directory.Exists(framingAssistantCachePath)) { + var subdirs = Directory.GetDirectories(framingAssistantCachePath); + foreach (var subdir in subdirs) { + var dirName = Path.GetFileName(subdir); + var cacheInfoPath = Path.Combine(subdir, "CacheInfo.xml"); + + // Only add if it has a CacheInfo.xml file + if (File.Exists(cacheInfoPath)) { + sources.Add(dirName); + } + } + } + } catch (Exception ex) { + Logger.Error("Error scanning cache sources", ex); + } + + return sources; + } + + /// + /// Loads cache from a specific source (subdirectory or default) + /// + public void LoadCacheSource(string source) { + try { + string cachePath; + string cacheInfoPath; + + if (source == Default || string.IsNullOrEmpty(source)) { + cachePath = framingAssistantCachePath; + cacheInfoPath = framingAssistantCachInfo; + } else { + cachePath = Path.Combine(framingAssistantCachePath, source); + cacheInfoPath = Path.Combine(cachePath, "CacheInfo.xml"); + } + + if (!Directory.Exists(cachePath)) { + Directory.CreateDirectory(cachePath); + } + + XElement cacheElement; + if (!File.Exists(cacheInfoPath)) { + cacheElement = new XElement("ImageCacheInfo"); + cacheElement.Save(cacheInfoPath); + } else { + cacheElement = XElement.Load(cacheInfoPath); + + // Ensure Backwards compatibility + var elements = cacheElement.Elements("Image").Where(x => x.Attribute("Id") == null); + foreach (var element in elements) { + element.Add(new XAttribute("Id", Guid.NewGuid())); + } + elements = cacheElement.Elements("Image").Where(x => x.Attribute("Source") == null); + foreach (var element in elements) { + if (element.Attribute("Rotation").Value != "0") { + element.Add(new XAttribute("Source", nameof(FileSkySurvey))); + } else { + element.Add(new XAttribute("Source", nameof(NASASkySurvey))); + } + } + } + + cachesBySource[source] = cacheElement; + cachePathsBySource[source] = cachePath; + + // set active source context + activeSource = string.IsNullOrEmpty(source) ? Default : source; + activeCachePath = cachePath; + } catch (Exception ex) { + Logger.Error($"Error loading cache source {source}", ex); + } + } + + public XElement GetActiveCacheElement() { + if (activeSource == Default || !cachesBySource.ContainsKey(activeSource)) { + return Cache; + } + return cachesBySource[activeSource]; + } } } \ No newline at end of file diff --git a/NINA.WPF.Base/SkySurvey/SkyMapAnnotator.cs b/NINA.WPF.Base/SkySurvey/SkyMapAnnotator.cs index 5e7505f95c..b82f841db1 100644 --- a/NINA.WPF.Base/SkySurvey/SkyMapAnnotator.cs +++ b/NINA.WPF.Base/SkySurvey/SkyMapAnnotator.cs @@ -1,7 +1,7 @@ #region "copyright" /* - Copyright © 2016 - 2026 Stefan Berg and the N.I.N.A. contributors + Copyright � 2016 - 2026 Stefan Berg and the N.I.N.A. contributors This file is part of N.I.N.A. - Nighttime Imaging 'N' Astronomy. @@ -300,7 +300,8 @@ private List GetCacheImagesForViewport() { double maxSize = 600; var l = new List(); - foreach (var entry in cache.Cache.Elements("Image")) { + var cacheDoc = cache?.GetActiveCacheElement(); + foreach (var entry in cacheDoc?.Elements("Image") ?? Enumerable.Empty()) { double fovW = double.Parse(entry.Attribute("FoVW").Value, CultureInfo.InvariantCulture); double fovH = double.Parse(entry.Attribute("FoVH").Value, CultureInfo.InvariantCulture); @@ -311,7 +312,7 @@ private List GetCacheImagesForViewport() { double ra = double.Parse(entry.Attribute("RA").Value, CultureInfo.InvariantCulture); double dec = double.Parse(entry.Attribute("Dec").Value, CultureInfo.InvariantCulture); double rotation = double.Parse(entry.Attribute("Rotation").Value, CultureInfo.InvariantCulture); - string path = Path.Combine(cache.framingAssistantCachePath, entry.Attribute("FileName").Value); + string path = Path.Combine(cache.ActiveCachePath, entry.Attribute("FileName").Value); if (AstroUtil.ArcminToArcsec(fovW) > minSize && AstroUtil.ArcminToArcsec(fovH) > minSize) { var existing = cacheImages.FirstOrDefault(x => x.Coordinates.RA == ra && x.Coordinates.Dec == dec); diff --git a/NINA/View/FramingAssistantView.xaml b/NINA/View/FramingAssistantView.xaml index f7435b69b6..fe2946c6a9 100644 --- a/NINA/View/FramingAssistantView.xaml +++ b/NINA/View/FramingAssistantView.xaml @@ -333,6 +333,35 @@ + + + + + + + + + + + + + + + + diff --git a/NINA/ViewModel/FramingAssistant/FramingAssistantVM.cs b/NINA/ViewModel/FramingAssistant/FramingAssistantVM.cs index 806838ed00..09fe4e79e6 100644 --- a/NINA/ViewModel/FramingAssistant/FramingAssistantVM.cs +++ b/NINA/ViewModel/FramingAssistant/FramingAssistantVM.cs @@ -447,6 +447,15 @@ private void InitializeCache() { Cache = new CacheSkySurvey(profileService.ActiveProfile.ApplicationSettings.SkySurveyCacheDirectory); ImageCacheInfo = Cache.Cache; _selectedImageCacheInfo = (XElement)ImageCacheInfo?.FirstNode ?? null; + + // Load available cache sources + var sources = Cache.GetAvailableCacheSources(); + if (!sources.SequenceEqual(new[] { CacheSkySurvey.Default })) + AvailableCacheSources = sources; + else + AvailableCacheSources = null; + SelectedCacheSource = CacheSkySurvey.Default; + RaisePropertyChanged(nameof(ImageCacheInfo)); } catch (Exception ex) { Logger.Error(ex); @@ -624,8 +633,7 @@ private void ClearCache(object obj) { SkyMapAnnotator.ClearImagesForViewport(); Cache.Clear(); - ImageCacheInfo = Cache.Cache; - RaisePropertyChanged(nameof(ImageCacheInfo)); + InitializeCache(); } } } @@ -902,6 +910,39 @@ public SkySurveySource FramingAssistantSource { } } + private List _availableCacheSources; + + public List AvailableCacheSources { + get => _availableCacheSources; + set { + _availableCacheSources = value; + RaisePropertyChanged(); + } + } + + private string _selectedCacheSource = CacheSkySurvey.Default; + + public string SelectedCacheSource { + get => _selectedCacheSource; + set { + if (_selectedCacheSource != value) { + _selectedCacheSource = value; + if (Cache != null) { + Cache.LoadCacheSource(value); + ImageCacheInfo = Cache.GetActiveCacheElement(); + RaisePropertyChanged(nameof(ImageCacheInfo)); + try { + SkyMapAnnotator?.ClearImagesForViewport(); + SkyMapAnnotator?.UpdateSkyMap(); + } catch { } + + LoadImage(); + } + RaisePropertyChanged(); + } + } + } + private double _cameraPixelSize; public double CameraPixelSize { @@ -1104,6 +1145,11 @@ private async Task LoadImage() { SkyMapAnnotator.UseCachedImages = false; } + // Load cache from selected source if SKYATLAS is selected + if (FramingAssistantSource == SkySurveySource.SKYATLAS && Cache != null) { + Cache.LoadCacheSource(SelectedCacheSource); + } + if (Cache != null && DSO != null) { try { skySurveyImage = await Cache.GetImage(FramingAssistantSource.GetCacheSourceString(), DSO.Coordinates.RA, DSO.Coordinates.Dec, 360 - DSO.RotationPositionAngle, AstroUtil.DegreeToArcmin(FieldOfView));