Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service isn't updating #2

Open
InteXX opened this issue Jan 29, 2018 · 1 comment
Open

Service isn't updating #2

InteXX opened this issue Jan 29, 2018 · 1 comment

Comments

@InteXX
Copy link

InteXX commented Jan 29, 2018

I'm trying to better understand the workings of this rather intriguing piece of code.

As it is, the service isn't updating. I've duplicated your configurations, e.g. SquirrelAwareApp, double-check Releases path, etc.

I should note that the service is throwing an exception during stop:

Failed to stop service. System.InvalidOperationException: An unhandled exception was detected ---> System.Exception: You must dispose UpdateManager!
at Squirrel.UpdateManager.Finalize()
--- End of inner exception stack trace ---
at Topshelf.Runtime.Windows.WindowsServiceHost.OnStop()
at System.ServiceProcess.ServiceBase.DeferredStop()

Paul recommends keeping UpdateManager around only for the duration of the actual updating action, as noted here. (At first I'd thought that this Disposing error only occurred during Debug, but now I see that it's happening even during Release.)

  • I see here that you're updating the app, but doesn't the Squirrel construct require a restart to apply an update?
  • Is this the call that performs the actual update? How do we get that to run? Should we be hooking into the Squirrel events, since this is a SquirrelAwareApp?
  • Where does the code modify the service's registry entry to point to the new release? Does Topshelf handle that?
  • What is the purpose of WithOverlapping?

That said, my test service isn't even acquiring the latest release from the distribution folder. I don't know whether that's related to the Disposing error discussed previously, but I probably doubt it.

Nice work on this. I'd like to contribute, but I'm not very handy with C# I'm afraid.

@InteXX
Copy link
Author

InteXX commented Jan 30, 2018

UPDATE

It works now.

The problem was due to an incorrect Releases path. The value of the string variable was correct, but I failed to account for it being modified in the code. Once I got that fixed, the service updated as expected.

This answers the first three of my questions, however I'm still curious about the intended usage of WithOverlapping. It appears to govern whether the service stays running during the update process, but I could be mistaken. Please advise.

Here's some updated code (partial credit to Nonobis) to fix the Disposing bug, as well as add important cancellation support:

namespace Topshelf.Squirrel.Windows.Interfaces
{
  public interface IUpdater
  {
    void Start();
    void Cancel();
  }
}
using System;
using System.Reflection;
using Topshelf.HostConfigurators;
using Topshelf.Squirrel.Windows.Builders;
using Topshelf.Squirrel.Windows.Interfaces;

namespace Topshelf.Squirrel.Windows
{
  public class SquirreledHost
  {
    private readonly string serviceName;
    private readonly string serviceDisplayName;
    private readonly bool withOverlapping;
    private readonly bool promtCredsWhileInstall;
    private readonly ISelfUpdatableService selfUpdatableService;
    private readonly IUpdater updater;

    public SquirreledHost(
      ISelfUpdatableService selfUpdatableService,
      string serviceName = null,
      string serviceDisplayName = null, IUpdater updater = null, bool withOverlapping = false, bool promtCredsWhileInstall = false)
    {
      var assemblyName = Assembly.GetEntryAssembly().GetName().Name;

      this.serviceName = serviceName ?? assemblyName;
      this.serviceDisplayName = serviceDisplayName ?? assemblyName;
      this.selfUpdatableService = selfUpdatableService;
      this.withOverlapping = withOverlapping;
      this.promtCredsWhileInstall = promtCredsWhileInstall;
      this.updater = updater;
    }

    public void ConfigureAndRun(ConfigureExt configureExt = null)
    {
      HostFactory.Run(configurator => { Configure(configurator); configureExt?.Invoke(configurator); });
    }

    public delegate void ConfigureExt(HostConfigurator config);

    private void Configure(HostConfigurator config)
    {
      config.Service<ISelfUpdatableService>(service =>
      {
        service.ConstructUsing(settings => selfUpdatableService);

        service.WhenStarted((s, hostControl) =>
        {
          s.Start();
          return true;
        });

        service.AfterStartingService(() => { updater?.Start(); });
        service.BeforeStoppingService(() => { updater?.Cancel(); });
        service.WhenStopped(s => { s.Stop(); });
      });

      config.SetServiceName(serviceName);
      config.SetDisplayName(serviceDisplayName);
      config.StartAutomatically();
      config.EnableShutdown();

      if (promtCredsWhileInstall)
      {
        config.RunAsFirstPrompt();
      }
      else
      {
        config.RunAsLocalSystem();
      }

      config.AddCommandLineSwitch("squirrel", _ => { });
      config.AddCommandLineDefinition("firstrun", _ => Environment.Exit(0));
      config.AddCommandLineDefinition("obsolete", _ => Environment.Exit(0));
      config.AddCommandLineDefinition("updated", version => { config.UseHostBuilder((env, settings) => new UpdateHostBuilder(env, settings, version, withOverlapping)); });
      config.AddCommandLineDefinition("install", version => { config.UseHostBuilder((env, settings) => new InstallAndStartHostBuilder(env, settings, version)); });
      config.AddCommandLineDefinition("uninstall", _ => { config.UseHostBuilder((env, settings) => new StopAndUninstallHostBuilder(env, settings)); });
    }
  }
}
using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using NuGet;
using Squirrel;
using Topshelf.Squirrel.Windows.Interfaces;

namespace Topshelf.Squirrel.Windows
{
  public class RepeatedTimeUpdater : IUpdater
  {
    private TimeSpan checkUpdatePeriod = TimeSpan.FromSeconds(30);
    private readonly IUpdateManager updateManager;
    private string curVersion;
    private string updateSource;
    private string appName;
    private CancellationTokenSource tokenSource;
    private Task updaterTask;


    /// <summary>
    /// Задать время между проверками доступности обновлений. По умолчанию 30 секунд.
    /// </summary>
    /// <param name="checkSpan"></param>
    /// <returns></returns>
    public RepeatedTimeUpdater SetCheckUpdatePeriod(TimeSpan checkSpan)
    {
      checkUpdatePeriod = checkSpan;
      return this;
    }

    [Obsolete("Will be removed")]
    public RepeatedTimeUpdater(IUpdateManager updateManager)
    {
      if (!Environment.UserInteractive)
      {
        if (updateManager == null)
          throw new Exception("Update manager can not be null");
      }
      curVersion = Assembly.GetEntryAssembly().GetName().Version.ToString();
      this.updateManager = updateManager;
    }

    public RepeatedTimeUpdater(string pUrlOrPath, string pApplicationName = null)
    {
      curVersion = Assembly.GetEntryAssembly().GetName().Version.ToString();
      updateSource = pUrlOrPath;
      appName = pApplicationName;
      tokenSource = new CancellationTokenSource();
    }

    /// <summary>
    /// Метод который проверяет обновления
    /// </summary>
    public void Start()
    {
      if (!Environment.UserInteractive)
      {
        updaterTask = Task.Run(Update);
        updaterTask.ConfigureAwait(false);
      }
    }

    public void Cancel()
    {
      if (!Environment.UserInteractive)
      {
        tokenSource.Cancel();
        updaterTask.Wait();
      }
    }

    [Obsolete("Will be removed")]
    private async Task OldUpdate()
    {
      if (updateManager == null)
        throw new Exception("Update manager can not be null");
      Trace.TraceInformation("Automatic-renewal was launched ({0})", curVersion);

      {
        while (true)
        {
          await Task.Delay(checkUpdatePeriod);
          try
          {
            //Проверяем наличие новой версии
            var update = await updateManager.CheckForUpdate();
            try
            {
              var oldVersion = update.CurrentlyInstalledVersion?.Version ?? new SemanticVersion(0, 0, 0, 0);
              var newVersion = update.FutureReleaseEntry.Version;
              if (oldVersion < newVersion)
              {
                Trace.TraceInformation("Found a new version: {0}", newVersion);

                //Скачиваем новую версию
                await updateManager.DownloadReleases(update.ReleasesToApply);

                //Распаковываем новую версию
                await updateManager.ApplyReleases(update);
              }
            }
            catch (Exception ex)
            {
              Trace.TraceError("Error on update ({0}): {1}", curVersion, ex);
            }
          }
          catch (Exception ex)
          {
            Trace.TraceError("Error on check for update ({0}): {1}", curVersion, ex);
          }
        }
      }
    }

    private async Task Update()
    {
      Trace.TraceInformation("Automatic-renewal was launched ({0})", curVersion);

      while (!tokenSource.Token.IsCancellationRequested)
      {
        await Task.Delay(checkUpdatePeriod);

        try
        {
          using (var upManager = new UpdateManager(updateSource, appName))
          {
            // Check for update
            var update = await upManager.CheckForUpdate();
            var oldVersion = update.CurrentlyInstalledVersion?.Version ?? new SemanticVersion(0, 0, 0, 0);
            Trace.TraceInformation("Installed version: {0}", oldVersion);

            var newVersion = update.FutureReleaseEntry?.Version;
            if (newVersion != null && oldVersion < newVersion)
            {
              Trace.TraceInformation("Found a new version: {0}", newVersion);

              // Downlaod Release
              await upManager.DownloadReleases(update.ReleasesToApply);

              // Apply Release
              await upManager.ApplyReleases(update);
            }
          }
        }
        catch (Exception ex)
        {
          Trace.TraceError("Error on check for update ({0}): {1}", curVersion, ex);
        }
      }
    }
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant