NetSparkleUpdater/NetSparkle

Question: Location of update config

christophwille opened this issue · 2 comments

In my trials I saw this ouput:

netsparkle: Starting update loop...
netsparkle: Reading config...
netsparkle: Update check performed within the last 1440 minutes!

Where is that information written to? (and would it be configurable?)

Good question. Depends on your OS. If Windows, see the RegistryConfiguration class. If other OS, see the JSONConfiguration class.

Nothing stopping you from using the JSONConfiguration class on all OS's, you'd just need to set/configure it as needed via the Configuration property on SparkleUpdater:

/// <summary>
/// The configuration object for a given assembly that has information on when
/// updates were checked last, any updates that have been skipped, etc.
/// </summary>
public Configuration Configuration
{
get
{
if (_configuration == null)
{
#if (NETSTANDARD || NET31 || NET5 || NET6 || NET7)
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_configuration = new RegistryConfiguration(new AssemblyReflectionAccessor(_appReferenceAssembly));
}
else
{
_configuration = new JSONConfiguration(new AssemblyReflectionAccessor(_appReferenceAssembly));
}
#else
_configuration = new RegistryConfiguration(new AssemblyReflectionAccessor(_appReferenceAssembly));
#endif
}
return _configuration;
}
set { _configuration = value; }
}

The path/location is configurable in either case. For JSONConfiguration, see the following:

/// <summary>
/// Get the full file path to the location and file name on disk
/// where the JSON configuration data should be saved.
/// By default, stored in <seealso cref="Environment.SpecialFolder.ApplicationData"/> in
/// the "NetSparkleUpdater" folder in the "data.json" file.
/// </summary>
/// <exception cref="NetSparkleException">Thrown when the assembly accessor does not have the company or product name
/// information available</exception>
public virtual string GetSavePath()
{
if (!string.IsNullOrEmpty(_savePath))
{
return _savePath;
}
else
{
if (string.IsNullOrEmpty(AssemblyAccessor.AssemblyCompany) || string.IsNullOrEmpty(AssemblyAccessor.AssemblyProduct))
{
throw new NetSparkleException("Error: NetSparkleUpdater is missing the company or product name tag in the assembly accessor ("
+ AssemblyAccessor.GetType() + ")");
}
var applicationFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.DoNotVerify);
var saveFolder = Path.Combine(applicationFolder, AssemblyAccessor.AssemblyCompany, AssemblyAccessor.AssemblyProduct, "NetSparkleUpdater");
if (!Directory.Exists(saveFolder))
{
Directory.CreateDirectory(saveFolder);
}
var saveLocation = Path.Combine(saveFolder, "data.json");
return saveLocation;
}
}

For RegistryConfiguration, see the following:

/// <summary>
/// Generate the path in the registry where data will be saved to/loaded from.
/// </summary>
/// <exception cref="NetSparkleException">Thrown when the assembly accessor does not have the company or product name
/// information available</exception>
public virtual string BuildRegistryPath()
{
if (!string.IsNullOrEmpty(_registryPath))
{
return _registryPath;
}
else
{
if (string.IsNullOrEmpty(AssemblyAccessor.AssemblyCompany) || string.IsNullOrEmpty(AssemblyAccessor.AssemblyProduct))
{
throw new NetSparkleException("Error: NetSparkleUpdater is missing the company or productname tag in the assembly accessor ("
+ AssemblyAccessor.GetType() + ")");
}
_registryPath = "Software\\";
if (!string.IsNullOrWhiteSpace(AssemblyAccessor.AssemblyCompany))
{
_registryPath += AssemblyAccessor.AssemblyCompany + "\\";
}
if (!string.IsNullOrWhiteSpace(AssemblyAccessor.AssemblyProduct))
{
_registryPath += AssemblyAccessor.AssemblyProduct + "\\";
}
_registryPath += "AutoUpdate";
return _registryPath;
}
}

So, basically, for the JSONConfiguration, you can simply pass in your own object with a different path in its constructor. Or for more complex use cases subclass JSONConfiguration and override the GetSavePath() method.

I tried with

_sparkle = new SparkleUpdater(
				"https://ilspy.net/appcast.xml",
				new Ed25519Checker(SecurityMode.Strict, "")
			) {
				UIFactory = new NetSparkleUpdater.UI.WPF.UIFactory(null),
				RelaunchAfterUpdate = false,
				CustomInstallerArguments = "",
				Configuration = new JSONConfiguration(null, sparkleSettingsPath)
			};

This will crash with

System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=NetSparkle
  StackTrace:
   at NetSparkleUpdater.Configurations.JSONConfiguration.GetSavePath()
   at NetSparkleUpdater.Configurations.JSONConfiguration..ctor(IAssemblyAccessor assemblyAccessor, String savePath)
   at ICSharpCode.ILSpy.MainWindow.MainWindow_Loaded(Object sender, RoutedEventArgs e) in D:\GitWorkspace\ILSpy\ILSpy\MainWindow.xaml.cs:line 912
   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
   at System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject root, RoutedEvent routedEvent)
   at System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(Object root)
   at System.Windows.Media.MediaContext.FireLoadedPendingCallbacks()
   at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
   at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
   at System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget)
   at System.Windows.Media.MediaContext.Resize(ICompositionTarget resizedCompositionTarget)
   at System.Windows.Interop.HwndTarget.OnResize()
   at System.Windows.Interop.HwndTarget.HandleMessage(WindowMessage msg, IntPtr wparam, IntPtr lparam)
   at System.Windows.Interop.HwndSource.HwndTargetFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.HwndSubclass.DefWndProcWrapper(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)

Did some digging and finally found it - ctor of JsonConfiguration:

_savePath = savePath != null && string.IsNullOrWhiteSpace(savePath) ? savePath : GetSavePath();

There is one small thing missing - the "not" (!) before string.IsNullOrWhiteSpace(savePath)