// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Linq;
using System.Threading;
using Duplicati.Server.Serialization;
namespace Duplicati.Server
{
///
/// The thread that checks on the update server if new versions are available
///
public class UpdatePollThread
{
private readonly Thread m_thread;
private volatile bool m_terminated = false;
private volatile bool m_download = false;
private volatile bool m_forceCheck = false;
private readonly object m_lock = new object();
private readonly AutoResetEvent m_waitSignal;
private double m_downloadProgress;
public bool IsUpdateRequested { get; private set; } = false;
public UpdatePollerStates ThreadState { get; private set; }
public double DownloadProgess
{
get { return m_downloadProgress ; }
private set
{
var oldv = m_downloadProgress;
m_downloadProgress = value;
if ((int)(oldv * 100) != (int)(value * 100))
Program.StatusEventNotifyer.SignalNewEvent();
}
}
public UpdatePollThread()
{
m_waitSignal = new AutoResetEvent(false);
ThreadState = UpdatePollerStates.Waiting;
m_thread = new Thread(Run);
m_thread.IsBackground = true;
m_thread.Name = "UpdatePollThread";
m_thread.Start();
}
public void CheckNow()
{
lock(m_lock)
{
m_forceCheck = true;
m_waitSignal.Set();
}
}
public void InstallUpdate()
{
lock(m_lock)
{
m_forceCheck = true;
m_download = true;
m_waitSignal.Set();
}
}
public void ActivateUpdate()
{
if (Duplicati.Library.AutoUpdater.UpdaterManager.SetRunUpdate())
{
IsUpdateRequested = true;
Program.ApplicationExitEvent.Set();
}
}
public void Terminate()
{
lock(m_lock)
{
m_terminated = true;
m_waitSignal.Set();
}
}
public void Reschedule()
{
m_waitSignal.Set();
}
private void Run()
{
// Wait on startup
m_waitSignal.WaitOne(TimeSpan.FromMinutes(1), true);
while (!m_terminated)
{
var nextCheck = Program.DataConnection.ApplicationSettings.NextUpdateCheck;
var maxcheck = TimeSpan.FromDays(7);
try
{
maxcheck = Library.Utility.Timeparser.ParseTimeSpan(Program.DataConnection.ApplicationSettings.UpdateCheckInterval);
}
catch
{
}
// If we have some weirdness, just check now
if (nextCheck - DateTime.UtcNow > maxcheck)
nextCheck = DateTime.UtcNow - TimeSpan.FromSeconds(1);
if (nextCheck < DateTime.UtcNow || m_forceCheck)
{
lock(m_lock)
m_forceCheck = false;
ThreadState = UpdatePollerStates.Checking;
Program.StatusEventNotifyer.SignalNewEvent();
DateTime started = DateTime.UtcNow;
Program.DataConnection.ApplicationSettings.LastUpdateCheck = started;
nextCheck = Program.DataConnection.ApplicationSettings.NextUpdateCheck;
Library.AutoUpdater.ReleaseType rt;
if (!Enum.TryParse(Program.DataConnection.ApplicationSettings.UpdateChannel, true, out rt))
rt = Duplicati.Library.AutoUpdater.ReleaseType.Unknown;
// Choose the default channel in case we have unknown
rt = rt == Duplicati.Library.AutoUpdater.ReleaseType.Unknown ? Duplicati.Library.AutoUpdater.AutoUpdateSettings.DefaultUpdateChannel : rt;
try
{
var update = Duplicati.Library.AutoUpdater.UpdaterManager.CheckForUpdate(rt);
if (update != null)
Program.DataConnection.ApplicationSettings.UpdatedVersion = update;
}
catch
{
}
// It could be that we have registered an update from a more unstable channel,
// but the user has switched to a more stable channel.
// In that case we discard the old update to avoid offering it.
if (Program.DataConnection.ApplicationSettings.UpdatedVersion != null)
{
Library.AutoUpdater.ReleaseType updatert;
var updatertstring = Program.DataConnection.ApplicationSettings.UpdatedVersion.ReleaseType;
if (string.Equals(updatertstring, "preview", StringComparison.OrdinalIgnoreCase))
updatertstring = Library.AutoUpdater.ReleaseType.Experimental.ToString();
if (!Enum.TryParse(updatertstring, true, out updatert))
updatert = Duplicati.Library.AutoUpdater.ReleaseType.Nightly;
if (updatert == Duplicati.Library.AutoUpdater.ReleaseType.Unknown)
updatert = Duplicati.Library.AutoUpdater.ReleaseType.Nightly;
if (updatert > rt)
Program.DataConnection.ApplicationSettings.UpdatedVersion = null;
}
if (Program.DataConnection.ApplicationSettings.UpdatedVersion != null && Duplicati.Library.AutoUpdater.UpdaterManager.TryParseVersion(Program.DataConnection.ApplicationSettings.UpdatedVersion.Version) > System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)
{
Program.DataConnection.RegisterNotification(
NotificationType.Information,
"Found update",
Program.DataConnection.ApplicationSettings.UpdatedVersion.Displayname,
null,
null,
"update:new",
null,
"NewUpdateFound",
null,
(self, all) => {
return all.FirstOrDefault(x => x.Action == "update:new") ?? self;
}
);
}
}
if (m_download)
{
lock(m_lock)
m_download = false;
var v = Program.DataConnection.ApplicationSettings.UpdatedVersion;
if (v != null)
{
ThreadState = UpdatePollerStates.Downloading;
Program.StatusEventNotifyer.SignalNewEvent();
if (Duplicati.Library.AutoUpdater.UpdaterManager.DownloadAndUnpackUpdate(v, (pg) => { DownloadProgess = pg; }))
Program.StatusEventNotifyer.SignalNewEvent();
}
}
DownloadProgess = 0;
if (ThreadState != UpdatePollerStates.Waiting)
{
ThreadState = UpdatePollerStates.Waiting;
Program.StatusEventNotifyer.SignalNewEvent();
}
var waitTime = nextCheck - DateTime.UtcNow;
// Guard against spin-loop
if (waitTime.TotalSeconds < 5)
waitTime = TimeSpan.FromSeconds(5);
// Guard against year-long waits
// A re-check does not cause an update check
if (waitTime.TotalDays > 1)
waitTime = TimeSpan.FromDays(1);
m_waitSignal.WaitOne(waitTime, true);
}
}
}
}