New: Set Branch, Update Mech from PackageInfo

pull/1689/head
Qstick 4 years ago
parent 8eea5a0ac9
commit 5ef2ec18d3

@ -101,6 +101,7 @@ class GeneralSettings extends Component {
isWindowsService, isWindowsService,
isDocker, isDocker,
mode, mode,
packageUpdateMechanism,
onInputChange, onInputChange,
onConfirmResetApiKey, onConfirmResetApiKey,
...otherProps ...otherProps
@ -163,6 +164,7 @@ class GeneralSettings extends Component {
advancedSettings={advancedSettings} advancedSettings={advancedSettings}
settings={settings} settings={settings}
isWindows={isWindows} isWindows={isWindows}
packageUpdateMechanism={packageUpdateMechanism}
isDocker={isDocker} isDocker={isDocker}
onInputChange={onInputChange} onInputChange={onInputChange}
/> />
@ -208,6 +210,7 @@ GeneralSettings.propTypes = {
isWindowsService: PropTypes.bool.isRequired, isWindowsService: PropTypes.bool.isRequired,
isDocker: PropTypes.bool.isRequired, isDocker: PropTypes.bool.isRequired,
mode: PropTypes.string.isRequired, mode: PropTypes.string.isRequired,
packageUpdateMechanism: PropTypes.string.isRequired,
onInputChange: PropTypes.func.isRequired, onInputChange: PropTypes.func.isRequired,
onConfirmResetApiKey: PropTypes.func.isRequired, onConfirmResetApiKey: PropTypes.func.isRequired,
onConfirmRestart: PropTypes.func.isRequired onConfirmRestart: PropTypes.func.isRequired

@ -28,6 +28,7 @@ function createMapStateToProps() {
isWindowsService: systemStatus.isWindows && systemStatus.mode === 'service', isWindowsService: systemStatus.isWindows && systemStatus.mode === 'service',
isDocker: systemStatus.isDocker, isDocker: systemStatus.isDocker,
mode: systemStatus.mode, mode: systemStatus.mode,
packageUpdateMechanism: systemStatus.packageUpdateMechanism,
...sectionSettings ...sectionSettings
}; };
} }

@ -1,22 +1,19 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import titleCase from 'Utilities/String/titleCase';
import { inputTypes, sizes } from 'Helpers/Props'; import { inputTypes, sizes } from 'Helpers/Props';
import FieldSet from 'Components/FieldSet'; import FieldSet from 'Components/FieldSet';
import FormGroup from 'Components/Form/FormGroup'; import FormGroup from 'Components/Form/FormGroup';
import FormLabel from 'Components/Form/FormLabel'; import FormLabel from 'Components/Form/FormLabel';
import FormInputGroup from 'Components/Form/FormInputGroup'; import FormInputGroup from 'Components/Form/FormInputGroup';
const branchValues = [
'develop',
'nightly'
];
function UpdateSettings(props) { function UpdateSettings(props) {
const { const {
advancedSettings, advancedSettings,
settings, settings,
isWindows, isWindows,
isDocker, isDocker,
packageUpdateMechanism,
onInputChange onInputChange
} = props; } = props;
@ -31,10 +28,20 @@ function UpdateSettings(props) {
return null; return null;
} }
const updateOptions = [ const usingExternalUpdateMechanism = packageUpdateMechanism !== 'builtIn';
{ key: 'builtIn', value: 'Built-In' },
{ key: 'script', value: 'Script' } const updateOptions = [];
];
if (usingExternalUpdateMechanism) {
updateOptions.push({
key: packageUpdateMechanism,
value: titleCase(packageUpdateMechanism)
});
} else {
updateOptions.push({ key: 'builtIn', value: 'Built-In' });
}
updateOptions.push({ key: 'script', value: 'Script' });
if (isDocker) { if (isDocker) {
return ( return (
@ -53,13 +60,13 @@ function UpdateSettings(props) {
<FormLabel>Branch</FormLabel> <FormLabel>Branch</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.AUTO_COMPLETE} type={inputTypes.TEXT}
name="branch" name="branch"
helpText="Branch to use to update Lidarr" helpText={usingExternalUpdateMechanism ? 'Branch used by external update mechanism' : 'Branch to use to update Lidarr'}
helpLink="https://github.com/Lidarr/Lidarr/wiki/Release-Branches" helpLink="https://github.com/Lidarr/Lidarr/wiki/Release-Branches"
{...branch} {...branch}
values={branchValues}
onChange={onInputChange} onChange={onInputChange}
readOnly={usingExternalUpdateMechanism}
/> />
</FormGroup> </FormGroup>
@ -127,6 +134,7 @@ UpdateSettings.propTypes = {
settings: PropTypes.object.isRequired, settings: PropTypes.object.isRequired,
isWindows: PropTypes.bool.isRequired, isWindows: PropTypes.bool.isRequired,
isDocker: PropTypes.bool.isRequired, isDocker: PropTypes.bool.isRequired,
packageUpdateMechanism: PropTypes.string.isRequired,
onInputChange: PropTypes.func.isRequired onInputChange: PropTypes.func.isRequired
}; };

@ -15,6 +15,8 @@ class About extends Component {
render() { render() {
const { const {
version, version,
packageVersion,
packageAuthor,
isNetCore, isNetCore,
isMono, isMono,
isDocker, isDocker,
@ -36,6 +38,14 @@ class About extends Component {
data={version} data={version}
/> />
{
packageVersion &&
<DescriptionListItem
title="Package Version"
data={(packageAuthor ? `${packageVersion} by ${packageAuthor}` : packageVersion)}
/>
}
{ {
isMono && isMono &&
<DescriptionListItem <DescriptionListItem
@ -99,6 +109,8 @@ class About extends Component {
About.propTypes = { About.propTypes = {
version: PropTypes.string.isRequired, version: PropTypes.string.isRequired,
packageVersion: PropTypes.string,
packageAuthor: PropTypes.string,
isNetCore: PropTypes.bool.isRequired, isNetCore: PropTypes.bool.isRequired,
isMono: PropTypes.bool.isRequired, isMono: PropTypes.bool.isRequired,
runtimeVersion: PropTypes.string.isRequired, runtimeVersion: PropTypes.string.isRequired,

@ -1,6 +1,6 @@
import _ from 'lodash'; import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component, Fragment } from 'react';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import formatDate from 'Utilities/Date/formatDate'; import formatDate from 'Utilities/Date/formatDate';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@ -25,6 +25,7 @@ class Updates extends Component {
error, error,
items, items,
isInstallingUpdate, isInstallingUpdate,
updateMechanism,
isDocker, isDocker,
shortDateFormat, shortDateFormat,
onInstallLatestPress onInstallLatestPress
@ -35,6 +36,12 @@ class Updates extends Component {
const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true }); const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true });
const noUpdateToInstall = hasUpdates && !hasUpdateToInstall; const noUpdateToInstall = hasUpdates && !hasUpdateToInstall;
const externalUpdaterMessages = {
external: 'Unable to update Lidarr directly, Lidarr is configured to use an external update mechanism',
apt: 'Unable to update Lidarr directly, use apt to install the update',
docker: 'Unable to update Lidarr directly, update the docker container to receive the update'
};
return ( return (
<PageContent title="Updates"> <PageContent title="Updates">
<PageContentBodyConnector> <PageContentBodyConnector>
@ -50,24 +57,29 @@ class Updates extends Component {
{ {
hasUpdateToInstall && hasUpdateToInstall &&
<div className={styles.updateAvailable}> <div className={styles.messageContainer}>
{ {
!isDocker && (updateMechanism === 'builtIn' || updateMechanism === 'script') && !isDocker ?
<SpinnerButton <SpinnerButton
className={styles.updateAvailable} className={styles.updateAvailable}
kind={kinds.PRIMARY} kind={kinds.PRIMARY}
isSpinning={isInstallingUpdate} isSpinning={isInstallingUpdate}
onPress={onInstallLatestPress} onPress={onInstallLatestPress}
> >
Install Latest Install Latest
</SpinnerButton> </SpinnerButton> :
}
<Fragment>
{ <Icon
isDocker && name={icons.WARNING}
<div className={styles.upToDateMessage}> kind={kinds.WARNING}
An update is available. Please update your Docker image and re-create the container. size={30}
</div> />
<div className={styles.message}>
{externalUpdaterMessages[updateMechanism] || externalUpdaterMessages.external}
</div>
</Fragment>
} }
{ {
@ -188,6 +200,7 @@ Updates.propTypes = {
items: PropTypes.array.isRequired, items: PropTypes.array.isRequired,
isInstallingUpdate: PropTypes.bool.isRequired, isInstallingUpdate: PropTypes.bool.isRequired,
isDocker: PropTypes.bool.isRequired, isDocker: PropTypes.bool.isRequired,
updateMechanism: PropTypes.string,
shortDateFormat: PropTypes.string.isRequired, shortDateFormat: PropTypes.string.isRequired,
onInstallLatestPress: PropTypes.func.isRequired onInstallLatestPress: PropTypes.func.isRequired
}; };

@ -18,6 +18,7 @@ namespace Lidarr.Api.V1.System
private readonly IConfigFileProvider _configFileProvider; private readonly IConfigFileProvider _configFileProvider;
private readonly IMainDatabase _database; private readonly IMainDatabase _database;
private readonly ILifecycleService _lifecycleService; private readonly ILifecycleService _lifecycleService;
private readonly IDeploymentInfoProvider _deploymentInfoProvider;
public SystemModule(IAppFolderInfo appFolderInfo, public SystemModule(IAppFolderInfo appFolderInfo,
IRuntimeInfo runtimeInfo, IRuntimeInfo runtimeInfo,
@ -26,7 +27,8 @@ namespace Lidarr.Api.V1.System
IRouteCacheProvider routeCacheProvider, IRouteCacheProvider routeCacheProvider,
IConfigFileProvider configFileProvider, IConfigFileProvider configFileProvider,
IMainDatabase database, IMainDatabase database,
ILifecycleService lifecycleService) ILifecycleService lifecycleService,
IDeploymentInfoProvider deploymentInfoProvider)
: base("system") : base("system")
{ {
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
@ -37,6 +39,7 @@ namespace Lidarr.Api.V1.System
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
_database = database; _database = database;
_lifecycleService = lifecycleService; _lifecycleService = lifecycleService;
_deploymentInfoProvider = deploymentInfoProvider;
Get("/status", x => GetStatus()); Get("/status", x => GetStatus());
Get("/routes", x => GetRoutes()); Get("/routes", x => GetRoutes());
Post("/shutdown", x => Shutdown()); Post("/shutdown", x => Shutdown());
@ -71,7 +74,10 @@ namespace Lidarr.Api.V1.System
UrlBase = _configFileProvider.UrlBase, UrlBase = _configFileProvider.UrlBase,
RuntimeVersion = _platformInfo.Version, RuntimeVersion = _platformInfo.Version,
RuntimeName = PlatformInfo.Platform, RuntimeName = PlatformInfo.Platform,
StartTime = _runtimeInfo.StartTime StartTime = _runtimeInfo.StartTime,
PackageVersion = _deploymentInfoProvider.PackageVersion,
PackageAuthor = _deploymentInfoProvider.PackageAuthor,
PackageUpdateMechanism = _deploymentInfoProvider.PackageUpdateMechanism
}; };
} }

@ -0,0 +1,105 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Update;
namespace NzbDrone.Core.Configuration
{
public interface IDeploymentInfoProvider
{
string PackageVersion { get; }
string PackageAuthor { get; }
string PackageBranch { get; }
UpdateMechanism PackageUpdateMechanism { get; }
string ReleaseVersion { get; }
string ReleaseBranch { get; }
bool IsExternalUpdateMechanism { get; }
UpdateMechanism DefaultUpdateMechanism { get; }
string DefaultBranch { get; }
}
public class DeploymentInfoProvider : IDeploymentInfoProvider
{
public DeploymentInfoProvider(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider)
{
var bin = appFolderInfo.StartUpFolder;
var packageInfoPath = Path.Combine(bin, "..", "package_info");
var releaseInfoPath = Path.Combine(bin, "release_info");
PackageUpdateMechanism = UpdateMechanism.BuiltIn;
DefaultBranch = "aphrodite";
if (Path.GetFileName(bin) == "bin" && diskProvider.FileExists(packageInfoPath))
{
var data = diskProvider.ReadAllText(packageInfoPath);
PackageVersion = ReadValue(data, "PackageVersion");
PackageAuthor = ReadValue(data, "PackageAuthor");
PackageUpdateMechanism = ReadEnumValue(data, "UpdateMethod", UpdateMechanism.BuiltIn);
PackageBranch = ReadValue(data, "Branch");
ReleaseVersion = ReadValue(data, "ReleaseVersion");
if (PackageBranch.IsNotNullOrWhiteSpace())
{
DefaultBranch = PackageBranch;
}
}
if (diskProvider.FileExists(releaseInfoPath))
{
var data = diskProvider.ReadAllText(releaseInfoPath);
ReleaseVersion = ReadValue(data, "ReleaseVersion", ReleaseVersion);
ReleaseBranch = ReadValue(data, "Branch");
if (ReleaseBranch.IsNotNullOrWhiteSpace())
{
DefaultBranch = ReleaseBranch;
}
}
DefaultUpdateMechanism = PackageUpdateMechanism;
}
private static string ReadValue(string fileData, string key, string defaultValue = null)
{
var match = Regex.Match(fileData, "^" + key + "=(.*)$", RegexOptions.Multiline);
if (match.Success)
{
return match.Groups[1].Value.Trim();
}
return defaultValue;
}
private static T ReadEnumValue<T>(string fileData, string key, T defaultValue)
where T : struct
{
var value = ReadValue(fileData, key);
if (value != null && Enum.TryParse<T>(value, true, out var result))
{
return result;
}
return defaultValue;
}
public string PackageVersion { get; private set; }
public string PackageAuthor { get; private set; }
public string PackageBranch { get; private set; }
public UpdateMechanism PackageUpdateMechanism { get; private set; }
public string ReleaseVersion { get; private set; }
public string ReleaseBranch { get; set; }
public bool IsExternalUpdateMechanism => PackageUpdateMechanism >= UpdateMechanism.External;
public UpdateMechanism DefaultUpdateMechanism { get; private set; }
public string DefaultBranch { get; private set; }
}
}

@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Update
{
public interface IUpdaterConfigProvider
{
}
public class UpdaterConfigProvider : IUpdaterConfigProvider, IHandle<ApplicationStartedEvent>
{
private readonly Logger _logger;
private readonly IConfigFileProvider _configFileProvider;
private readonly IDeploymentInfoProvider _deploymentInfoProvider;
public UpdaterConfigProvider(IDeploymentInfoProvider deploymentInfoProvider, IConfigFileProvider configFileProvider, Logger logger)
{
_deploymentInfoProvider = deploymentInfoProvider;
_configFileProvider = configFileProvider;
_logger = logger;
}
public void Handle(ApplicationStartedEvent message)
{
var updateMechanism = _configFileProvider.UpdateMechanism;
var packageUpdateMechanism = _deploymentInfoProvider.PackageUpdateMechanism;
var externalMechanisms = Enum.GetValues(typeof(UpdateMechanism))
.Cast<UpdateMechanism>()
.Where(v => v >= UpdateMechanism.External)
.ToArray();
foreach (var externalMechanism in externalMechanisms)
{
if ((packageUpdateMechanism != externalMechanism && updateMechanism == externalMechanism) ||
(packageUpdateMechanism == externalMechanism && updateMechanism == UpdateMechanism.BuiltIn))
{
_logger.Info("Update mechanism {0} not supported in the current configuration, changing to {1}.", updateMechanism, packageUpdateMechanism);
ChangeUpdateMechanism(packageUpdateMechanism);
break;
}
}
if (_deploymentInfoProvider.IsExternalUpdateMechanism)
{
var currentBranch = _configFileProvider.Branch;
var packageBranch = _deploymentInfoProvider.PackageBranch;
if (packageBranch.IsNotNullOrWhiteSpace() && packageBranch != currentBranch)
{
_logger.Info("External updater uses branch {0} instead of the currently selected {1}, changing to {0}.", packageBranch, currentBranch);
ChangeBranch(packageBranch);
}
}
}
private void ChangeUpdateMechanism(UpdateMechanism updateMechanism)
{
var config = new Dictionary<string, object>
{
[nameof(_configFileProvider.UpdateMechanism)] = updateMechanism
};
_configFileProvider.SaveConfigDictionary(config);
}
private void ChangeBranch(string branch)
{
var config = new Dictionary<string, object>
{
[nameof(_configFileProvider.Branch)] = branch
};
_configFileProvider.SaveConfigDictionary(config);
}
}
}

@ -29,6 +29,7 @@ namespace NzbDrone.Core.Update
private readonly IProcessProvider _processProvider; private readonly IProcessProvider _processProvider;
private readonly IVerifyUpdates _updateVerifier; private readonly IVerifyUpdates _updateVerifier;
private readonly IStartupContext _startupContext; private readonly IStartupContext _startupContext;
private readonly IDeploymentInfoProvider _deploymentInfoProvider;
private readonly IConfigFileProvider _configFileProvider; private readonly IConfigFileProvider _configFileProvider;
private readonly IRuntimeInfo _runtimeInfo; private readonly IRuntimeInfo _runtimeInfo;
private readonly IBackupService _backupService; private readonly IBackupService _backupService;
@ -43,6 +44,7 @@ namespace NzbDrone.Core.Update
IProcessProvider processProvider, IProcessProvider processProvider,
IVerifyUpdates updateVerifier, IVerifyUpdates updateVerifier,
IStartupContext startupContext, IStartupContext startupContext,
IDeploymentInfoProvider deploymentInfoProvider,
IConfigFileProvider configFileProvider, IConfigFileProvider configFileProvider,
IRuntimeInfo runtimeInfo, IRuntimeInfo runtimeInfo,
IBackupService backupService, IBackupService backupService,
@ -63,6 +65,7 @@ namespace NzbDrone.Core.Update
_processProvider = processProvider; _processProvider = processProvider;
_updateVerifier = updateVerifier; _updateVerifier = updateVerifier;
_startupContext = startupContext; _startupContext = startupContext;
_deploymentInfoProvider = deploymentInfoProvider;
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
_runtimeInfo = runtimeInfo; _runtimeInfo = runtimeInfo;
_backupService = backupService; _backupService = backupService;
@ -237,6 +240,18 @@ namespace NzbDrone.Core.Update
return; return;
} }
// Safety net, ConfigureUpdateMechanism should take care of invalid settings
if (_configFileProvider.UpdateMechanism == UpdateMechanism.BuiltIn && _deploymentInfoProvider.IsExternalUpdateMechanism)
{
_logger.ProgressDebug("Built-In updater disabled, please use {0} to install", _deploymentInfoProvider.PackageUpdateMechanism);
return;
}
else if (_configFileProvider.UpdateMechanism != UpdateMechanism.Script && _deploymentInfoProvider.IsExternalUpdateMechanism)
{
_logger.ProgressDebug("Update available, please use {0} to install", _deploymentInfoProvider.PackageUpdateMechanism);
return;
}
try try
{ {
InstallUpdate(latestAvailable); InstallUpdate(latestAvailable);

@ -1,8 +1,11 @@
namespace NzbDrone.Core.Update namespace NzbDrone.Core.Update
{ {
public enum UpdateMechanism public enum UpdateMechanism
{ {
BuiltIn = 0, BuiltIn = 0,
Script = 1 Script = 1,
External = 10,
Apt = 11,
Docker = 12
} }
} }

Loading…
Cancel
Save