2018-03-10 08:05:56 +00:00
|
|
|
|
using System;
|
2017-04-15 08:45:10 +00:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Net.Security;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2018-03-10 08:05:56 +00:00
|
|
|
|
using ICSharpCode.SharpZipLib.GZip;
|
|
|
|
|
using ICSharpCode.SharpZipLib.Tar;
|
|
|
|
|
using ICSharpCode.SharpZipLib.Zip;
|
2018-06-11 07:17:56 +00:00
|
|
|
|
using Jackett.Common.Models.Config;
|
2018-03-10 08:05:56 +00:00
|
|
|
|
using Jackett.Common.Models.GitHub;
|
|
|
|
|
using Jackett.Common.Services.Interfaces;
|
2018-06-11 07:17:56 +00:00
|
|
|
|
using Jackett.Common.Utils;
|
2018-03-10 08:05:56 +00:00
|
|
|
|
using Jackett.Common.Utils.Clients;
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
using NLog;
|
2017-04-15 08:45:10 +00:00
|
|
|
|
|
2018-03-10 08:05:56 +00:00
|
|
|
|
namespace Jackett.Common.Services
|
2017-04-15 08:45:10 +00:00
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
public class UpdateService: IUpdateService
|
|
|
|
|
{
|
|
|
|
|
Logger logger;
|
2017-11-05 09:42:03 +00:00
|
|
|
|
WebClient client;
|
2017-04-15 08:45:10 +00:00
|
|
|
|
IConfigurationService configService;
|
|
|
|
|
ManualResetEvent locker = new ManualResetEvent(false);
|
|
|
|
|
ITrayLockService lockService;
|
2018-06-11 07:17:56 +00:00
|
|
|
|
private ServerConfig serverConfig;
|
2017-04-15 08:45:10 +00:00
|
|
|
|
bool forceupdatecheck = false;
|
|
|
|
|
|
2018-06-11 07:17:56 +00:00
|
|
|
|
public UpdateService(Logger l, WebClient c, IConfigurationService cfg, ITrayLockService ls, ServerConfig sc)
|
2017-04-15 08:45:10 +00:00
|
|
|
|
{
|
|
|
|
|
logger = l;
|
|
|
|
|
client = c;
|
|
|
|
|
configService = cfg;
|
|
|
|
|
lockService = ls;
|
2018-06-11 07:17:56 +00:00
|
|
|
|
serverConfig = sc;
|
2017-04-15 08:45:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string ExePath()
|
|
|
|
|
{
|
|
|
|
|
var location = new Uri(Assembly.GetEntryAssembly().GetName().CodeBase);
|
|
|
|
|
return new FileInfo(location.AbsolutePath).FullName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void StartUpdateChecker()
|
|
|
|
|
{
|
|
|
|
|
Task.Factory.StartNew(UpdateWorkerThread);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void CheckForUpdatesNow()
|
|
|
|
|
{
|
|
|
|
|
forceupdatecheck = true;
|
|
|
|
|
locker.Set();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async void UpdateWorkerThread()
|
|
|
|
|
{
|
2018-04-17 09:35:34 +00:00
|
|
|
|
var delayHours = 1; // first check after 1 hour (for users not running jackett 24/7)
|
2017-04-15 08:45:10 +00:00
|
|
|
|
while (true)
|
|
|
|
|
{
|
2018-04-17 09:35:34 +00:00
|
|
|
|
locker.WaitOne((int)new TimeSpan(delayHours, 0, 0).TotalMilliseconds);
|
2017-04-15 08:45:10 +00:00
|
|
|
|
locker.Reset();
|
|
|
|
|
await CheckForUpdates();
|
2018-04-17 09:35:34 +00:00
|
|
|
|
delayHours = 24; // following checks only once/24 hours
|
2017-04-15 08:45:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool AcceptCert(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task CheckForUpdates()
|
|
|
|
|
{
|
2018-06-11 07:17:56 +00:00
|
|
|
|
if (serverConfig.RuntimeSettings.NoUpdates)
|
2018-02-09 16:30:07 +00:00
|
|
|
|
{
|
|
|
|
|
logger.Info($"Updates are disabled via --NoUpdates.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-06-11 07:17:56 +00:00
|
|
|
|
if (serverConfig.UpdateDisabled && !forceupdatecheck)
|
2017-04-15 08:45:10 +00:00
|
|
|
|
{
|
|
|
|
|
logger.Info($"Skipping update check as it is disabled.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
forceupdatecheck = true;
|
|
|
|
|
|
|
|
|
|
var isWindows = System.Environment.OSVersion.Platform != PlatformID.Unix;
|
|
|
|
|
if (Debugger.IsAttached)
|
|
|
|
|
{
|
|
|
|
|
logger.Info($"Skipping checking for new releases as the debugger is attached.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-24 01:31:08 +00:00
|
|
|
|
bool trayIsRunning = false;
|
|
|
|
|
if (isWindows)
|
|
|
|
|
{
|
|
|
|
|
trayIsRunning = Process.GetProcessesByName("JackettTray").Length > 0;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-15 08:45:10 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
var response = await client.GetString(new WebRequest()
|
|
|
|
|
{
|
|
|
|
|
Url = "https://api.github.com/repos/Jackett/Jackett/releases",
|
|
|
|
|
Encoding = Encoding.UTF8,
|
|
|
|
|
EmulateBrowser = false
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if(response.Status != System.Net.HttpStatusCode.OK)
|
|
|
|
|
{
|
|
|
|
|
logger.Error("Failed to get the release list: " + response.Status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var releases = JsonConvert.DeserializeObject<List<Release>>(response.Content);
|
|
|
|
|
|
2018-06-11 07:17:56 +00:00
|
|
|
|
if (!serverConfig.UpdatePrerelease)
|
2017-04-15 08:45:10 +00:00
|
|
|
|
{
|
|
|
|
|
releases = releases.Where(r => !r.Prerelease).ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (releases.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
var latestRelease = releases.OrderByDescending(o => o.Created_at).First();
|
|
|
|
|
var currentVersion = $"v{GetCurrentVersion()}";
|
|
|
|
|
|
|
|
|
|
if (latestRelease.Name != currentVersion && currentVersion != "v0.0.0.0")
|
|
|
|
|
{
|
|
|
|
|
logger.Info($"New release found. Current: {currentVersion} New: {latestRelease.Name}");
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var tempDir = await DownloadRelease(latestRelease.Assets, isWindows, latestRelease.Name);
|
|
|
|
|
// Copy updater
|
|
|
|
|
var installDir = Path.GetDirectoryName(ExePath());
|
|
|
|
|
var updaterPath = Path.Combine(tempDir, "Jackett", "JackettUpdater.exe");
|
|
|
|
|
if (updaterPath != null)
|
2018-06-24 01:31:08 +00:00
|
|
|
|
StartUpdate(updaterPath, installDir, isWindows, serverConfig.RuntimeSettings.NoRestart, trayIsRunning);
|
2017-04-15 08:45:10 +00:00
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
logger.Error(e, "Error performing update.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-09-13 08:35:51 +00:00
|
|
|
|
logger.Info($"Checked for a updated release but none was found. Current: {currentVersion} Latest: {latestRelease.Name}");
|
2017-04-15 08:45:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
logger.Error(e, "Error checking for updates.");
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
if (!isWindows)
|
|
|
|
|
{
|
|
|
|
|
System.Net.ServicePointManager.ServerCertificateValidationCallback -= AcceptCert;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string GetCurrentVersion()
|
|
|
|
|
{
|
2017-11-14 19:43:42 +00:00
|
|
|
|
var assembly = Assembly.GetExecutingAssembly();
|
2017-04-15 08:45:10 +00:00
|
|
|
|
var fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
|
2017-11-08 08:53:00 +00:00
|
|
|
|
return fvi.ProductVersion;
|
2017-04-15 08:45:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private WebRequest SetDownloadHeaders(WebRequest req)
|
|
|
|
|
{
|
|
|
|
|
req.Headers = new Dictionary<string, string>()
|
|
|
|
|
{
|
|
|
|
|
{ "Accept", "application/octet-stream" }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return req;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void CleanupTempDir()
|
|
|
|
|
{
|
|
|
|
|
var tempDir = Path.GetTempPath();
|
|
|
|
|
|
|
|
|
|
if (!Directory.Exists(tempDir))
|
|
|
|
|
{
|
|
|
|
|
logger.Error("Temp dir doesn't exist: " + tempDir.ToString());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
DirectoryInfo d = new DirectoryInfo(tempDir);
|
|
|
|
|
foreach (var dir in d.GetDirectories("JackettUpdate-*"))
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
logger.Info("Deleting JackettUpdate temp files from " + dir.FullName);
|
|
|
|
|
dir.Delete(true);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
logger.Error("Error while deleting temp files from " + dir.FullName);
|
|
|
|
|
logger.Error(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
logger.Error("Unexpected error while deleting temp files from " + tempDir.ToString());
|
|
|
|
|
logger.Error(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<string> DownloadRelease(List<Asset> assets, bool isWindows, string version)
|
|
|
|
|
{
|
2018-06-24 01:31:08 +00:00
|
|
|
|
var targetAsset = assets.Where(a => isWindows ? a.Browser_download_url.EndsWith(".zip", StringComparison.OrdinalIgnoreCase) : a.Browser_download_url.EndsWith(".gz", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
|
2017-04-15 08:45:10 +00:00
|
|
|
|
|
|
|
|
|
if (targetAsset == null)
|
|
|
|
|
{
|
|
|
|
|
logger.Error("Failed to find asset to download!");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var url = targetAsset.Browser_download_url;
|
|
|
|
|
|
|
|
|
|
var data = await client.GetBytes(SetDownloadHeaders(new WebRequest() { Url = url, EmulateBrowser = true, Type = RequestType.GET }));
|
|
|
|
|
|
|
|
|
|
while (data.IsRedirect)
|
|
|
|
|
{
|
|
|
|
|
data = await client.GetBytes(new WebRequest() { Url = data.RedirectingTo, EmulateBrowser = true, Type = RequestType.GET });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var tempDir = Path.Combine(Path.GetTempPath(), "JackettUpdate-" + version + "-" + DateTime.Now.Ticks);
|
|
|
|
|
|
|
|
|
|
if (Directory.Exists(tempDir))
|
|
|
|
|
{
|
|
|
|
|
Directory.Delete(tempDir, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Directory.CreateDirectory(tempDir);
|
|
|
|
|
|
|
|
|
|
if (isWindows)
|
|
|
|
|
{
|
|
|
|
|
var zipPath = Path.Combine(tempDir, "Update.zip");
|
|
|
|
|
File.WriteAllBytes(zipPath, data.Content);
|
|
|
|
|
var fastZip = new FastZip();
|
|
|
|
|
fastZip.ExtractZip(zipPath, tempDir, null);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var gzPath = Path.Combine(tempDir, "Update.tar.gz");
|
|
|
|
|
File.WriteAllBytes(gzPath, data.Content);
|
|
|
|
|
Stream inStream = File.OpenRead(gzPath);
|
|
|
|
|
Stream gzipStream = new GZipInputStream(inStream);
|
|
|
|
|
|
|
|
|
|
TarArchive tarArchive = TarArchive.CreateInputTarArchive(gzipStream);
|
|
|
|
|
tarArchive.ExtractContents(tempDir);
|
|
|
|
|
tarArchive.Close();
|
|
|
|
|
gzipStream.Close();
|
|
|
|
|
inStream.Close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return tempDir;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-24 01:31:08 +00:00
|
|
|
|
private void StartUpdate(string updaterExePath, string installLocation, bool isWindows, bool NoRestart, bool trayWasRunning)
|
2017-04-15 08:45:10 +00:00
|
|
|
|
{
|
2017-11-14 19:31:27 +00:00
|
|
|
|
var exe = Path.GetFileName(ExePath());
|
2017-12-04 11:20:22 +00:00
|
|
|
|
var args = string.Join(" ", Environment.GetCommandLineArgs().Skip(1).Select(a => a.Contains(" ") ? "\"" +a + "\"" : a )).Replace("\"", "\\\"");
|
2017-04-15 08:45:10 +00:00
|
|
|
|
|
|
|
|
|
var startInfo = new ProcessStartInfo();
|
|
|
|
|
|
|
|
|
|
// Note: add a leading space to the --Args argument to avoid parsing as arguments
|
|
|
|
|
if (isWindows)
|
|
|
|
|
{
|
|
|
|
|
startInfo.Arguments = $"--Path \"{installLocation}\" --Type \"{exe}\" --Args \" {args}\"";
|
|
|
|
|
startInfo.FileName = Path.Combine(updaterExePath);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Wrap mono
|
|
|
|
|
args = exe + " " + args;
|
|
|
|
|
exe = "mono";
|
|
|
|
|
|
|
|
|
|
startInfo.Arguments = $"{Path.Combine(updaterExePath)} --Path \"{installLocation}\" --Type \"{exe}\" --Args \" {args}\"";
|
|
|
|
|
startInfo.FileName = "mono";
|
|
|
|
|
startInfo.UseShellExecute = false;
|
|
|
|
|
startInfo.CreateNoWindow = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
var pid = Process.GetCurrentProcess().Id;
|
|
|
|
|
startInfo.Arguments += $" --KillPids \"{pid}\"";
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
logger.Error("Unexpected error while retriving the PID");
|
|
|
|
|
logger.Error(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (NoRestart)
|
2018-06-24 01:31:08 +00:00
|
|
|
|
{
|
2017-04-15 08:45:10 +00:00
|
|
|
|
startInfo.Arguments += " --NoRestart";
|
2018-06-24 01:31:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (trayWasRunning)
|
|
|
|
|
{
|
|
|
|
|
startInfo.Arguments += " --StartTray";
|
|
|
|
|
}
|
2017-04-15 08:45:10 +00:00
|
|
|
|
|
2017-11-15 18:00:10 +00:00
|
|
|
|
logger.Info($"Starting updater: {startInfo.FileName} {startInfo.Arguments}");
|
2017-04-15 08:45:10 +00:00
|
|
|
|
var procInfo = Process.Start(startInfo);
|
|
|
|
|
logger.Info($"Updater started process id: {procInfo.Id}");
|
|
|
|
|
if (NoRestart == false)
|
|
|
|
|
{
|
|
|
|
|
logger.Info("Exiting Jackett..");
|
|
|
|
|
lockService.Signal();
|
2018-06-11 07:17:56 +00:00
|
|
|
|
//TODO: Remove once off Owin
|
|
|
|
|
if (EnvironmentUtil.IsRunningLegacyOwin)
|
|
|
|
|
{
|
|
|
|
|
Engine.Exit(0);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Environment.Exit(0);
|
|
|
|
|
}
|
2017-04-15 08:45:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|