2018-04-08 04:02:55 +00:00
using System ;
2017-09-19 04:21:26 +00:00
using System.IO ;
using System.Linq ;
2014-01-06 06:20:08 +00:00
using System.Security.AccessControl ;
using System.Security.Principal ;
using NLog ;
using NzbDrone.Common.Disk ;
2017-07-28 21:40:12 +00:00
using NzbDrone.Common.Exceptions ;
2017-09-19 04:21:26 +00:00
using NzbDrone.Common.Extensions ;
2014-01-06 06:20:08 +00:00
using NzbDrone.Common.Instrumentation ;
namespace NzbDrone.Common.EnvironmentInfo
{
public interface IAppFolderFactory
{
void Register ( ) ;
}
public class AppFolderFactory : IAppFolderFactory
{
private readonly IAppFolderInfo _appFolderInfo ;
2017-09-19 04:21:26 +00:00
private readonly IStartupContext _startupContext ;
2014-01-06 06:20:08 +00:00
private readonly IDiskProvider _diskProvider ;
2017-09-19 04:21:26 +00:00
private readonly IDiskTransferService _diskTransferService ;
2014-01-06 06:20:08 +00:00
private readonly Logger _logger ;
2017-09-19 04:21:26 +00:00
public AppFolderFactory ( IAppFolderInfo appFolderInfo ,
IStartupContext startupContext ,
IDiskProvider diskProvider ,
IDiskTransferService diskTransferService )
2014-01-06 06:20:08 +00:00
{
_appFolderInfo = appFolderInfo ;
2017-09-19 04:21:26 +00:00
_startupContext = startupContext ;
2014-01-06 06:20:08 +00:00
_diskProvider = diskProvider ;
2017-09-19 04:21:26 +00:00
_diskTransferService = diskTransferService ;
2014-01-06 06:20:08 +00:00
_logger = NzbDroneLogger . GetLogger ( this ) ;
}
public void Register ( )
{
2018-04-08 04:02:55 +00:00
try
{
2017-09-19 04:21:26 +00:00
MigrateAppDataFolder ( ) ;
2018-04-08 04:02:55 +00:00
_diskProvider . EnsureFolder ( _appFolderInfo . AppDataFolder ) ;
}
catch ( UnauthorizedAccessException )
{
throw new SonarrStartupException ( "Cannot create AppFolder, Access to the path {0} is denied" , _appFolderInfo . AppDataFolder ) ;
}
2014-01-06 06:20:08 +00:00
2014-12-07 20:54:07 +00:00
if ( OsInfo . IsWindows )
2014-01-06 06:20:08 +00:00
{
SetPermissions ( ) ;
}
2017-07-28 21:40:12 +00:00
if ( ! _diskProvider . FolderWritable ( _appFolderInfo . AppDataFolder ) )
{
throw new SonarrStartupException ( "AppFolder {0} is not writable" , _appFolderInfo . AppDataFolder ) ;
}
2019-03-09 21:33:34 +00:00
InitializeMonoApplicationData ( ) ;
2014-01-06 06:20:08 +00:00
}
private void SetPermissions ( )
{
try
{
2019-12-24 16:18:07 +00:00
_diskProvider . SetEveryonePermissions ( _appFolderInfo . AppDataFolder ) ;
2014-01-06 06:20:08 +00:00
}
catch ( Exception ex )
{
2016-02-11 21:13:42 +00:00
_logger . Warn ( ex , "Coudn't set app folder permission" ) ;
2014-01-06 06:20:08 +00:00
}
}
2017-09-19 04:21:26 +00:00
private void MigrateAppDataFolder ( )
{
try
{
var oldDbFile = Path . Combine ( _appFolderInfo . AppDataFolder , "nzbdrone.db" ) ;
if ( _startupContext . Args . ContainsKey ( StartupContext . APPDATA ) )
{
2021-07-30 04:47:53 +00:00
if ( _diskProvider . FileExists ( _appFolderInfo . GetDatabase ( ) ) )
{
return ;
}
if ( ! _diskProvider . FileExists ( oldDbFile ) )
{
return ;
}
2017-09-19 04:21:26 +00:00
2021-03-18 00:26:12 +00:00
MoveSqliteDatabase ( oldDbFile , _appFolderInfo . GetDatabase ( ) ) ;
2017-09-19 04:21:26 +00:00
RemovePidFile ( ) ;
}
if ( _appFolderInfo . LegacyAppDataFolder . IsNullOrWhiteSpace ( ) ) return ;
if ( _diskProvider . FileExists ( _appFolderInfo . GetDatabase ( ) ) | | _diskProvider . FileExists ( _appFolderInfo . GetConfigPath ( ) ) ) return ;
if ( ! _diskProvider . FolderExists ( _appFolderInfo . LegacyAppDataFolder ) ) return ;
// Delete the bin folder on Windows
var binFolder = Path . Combine ( _appFolderInfo . LegacyAppDataFolder , "bin" ) ;
if ( OsInfo . IsWindows & & _diskProvider . FolderExists ( binFolder ) )
{
_diskProvider . DeleteFolder ( binFolder , true ) ;
}
// Transfer other files and folders (with copy so a backup is maintained)
_diskTransferService . TransferFolder ( _appFolderInfo . LegacyAppDataFolder , _appFolderInfo . AppDataFolder , TransferMode . Copy ) ;
// Rename the DB file
if ( _diskProvider . FileExists ( oldDbFile ) )
{
2021-03-18 00:26:12 +00:00
MoveSqliteDatabase ( oldDbFile , _appFolderInfo . GetDatabase ( ) ) ;
2017-09-19 04:21:26 +00:00
}
// Remove Old PID file
RemovePidFile ( ) ;
// Delete the old files after everything has been copied
_diskProvider . DeleteFolder ( _appFolderInfo . LegacyAppDataFolder , true ) ;
}
catch ( Exception ex )
{
_logger . Debug ( ex , ex . Message ) ;
throw new SonarrStartupException ( "Unable to migrate AppData folder from {0} to {1}. Migrate manually" , _appFolderInfo . LegacyAppDataFolder , _appFolderInfo . AppDataFolder ) ;
}
}
2019-03-09 21:33:34 +00:00
private void InitializeMonoApplicationData ( )
{
2021-07-30 04:47:53 +00:00
if ( OsInfo . IsWindows )
{
return ;
}
2019-03-09 21:33:34 +00:00
2019-03-12 07:55:01 +00:00
try
2019-03-09 21:33:34 +00:00
{
2021-07-30 04:47:53 +00:00
// It seems that DoNotVerify is the mono behaviour even though .net docs specify a blank string
// should be returned if the data doesn't exist. For compatibility with .net core, explicitly
// set DoNotVerify (which makes sense given we're explicitly checking that the folder exists)
var configHome = Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData , Environment . SpecialFolderOption . DoNotVerify ) ;
if ( configHome . IsNullOrWhiteSpace ( ) | |
configHome = = "/.config" | |
( configHome . EndsWith ( "/.config" ) & & ! _diskProvider . FolderExists ( configHome . GetParentPath ( ) ) ) | |
2019-03-12 07:55:01 +00:00
! _diskProvider . FolderExists ( configHome ) )
{
2021-07-30 04:47:53 +00:00
// Tell mono/netcore to use appData/.config as ApplicationData folder.
2019-03-12 07:55:01 +00:00
Environment . SetEnvironmentVariable ( "XDG_CONFIG_HOME" , Path . Combine ( _appFolderInfo . AppDataFolder , ".config" ) ) ;
}
2019-03-09 21:33:34 +00:00
2021-07-30 04:47:53 +00:00
var dataHome = Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData , Environment . SpecialFolderOption . DoNotVerify ) ;
if ( dataHome . IsNullOrWhiteSpace ( ) | |
dataHome = = "/.local/share" | |
( dataHome . EndsWith ( "/.local/share" ) & & ! _diskProvider . FolderExists ( dataHome . GetParentPath ( ) . GetParentPath ( ) ) ) | |
2019-03-12 07:55:01 +00:00
! _diskProvider . FolderExists ( dataHome ) )
{
2021-07-30 04:47:53 +00:00
// Tell mono/netcore to use appData/.config/share as LocalApplicationData folder.
2019-03-12 07:55:01 +00:00
Environment . SetEnvironmentVariable ( "XDG_DATA_HOME" , Path . Combine ( _appFolderInfo . AppDataFolder , ".config/share" ) ) ;
}
}
catch ( Exception ex )
2019-03-09 21:33:34 +00:00
{
2019-03-12 07:55:01 +00:00
_logger . Warn ( ex , "Failed to initialize the mono config directory." ) ;
2019-03-09 21:33:34 +00:00
}
}
2021-03-18 00:26:12 +00:00
private void MoveSqliteDatabase ( string source , string destination )
2017-09-19 04:21:26 +00:00
{
2021-03-18 00:26:12 +00:00
_logger . Info ( "Moving {0}* to {1}*" , source , destination ) ;
var dbSuffixes = new [ ] { "" , "-shm" , "-wal" , "-journal" } ;
foreach ( var suffix in dbSuffixes )
{
var sourceFile = source + suffix ;
var destFile = destination + suffix ;
if ( _diskProvider . FileExists ( destFile ) )
{
_diskProvider . DeleteFile ( destFile ) ;
}
if ( _diskProvider . FileExists ( sourceFile ) )
{
_diskProvider . CopyFile ( sourceFile , destFile ) ;
}
}
foreach ( var suffix in dbSuffixes )
{
var sourceFile = source + suffix ;
if ( _diskProvider . FileExists ( sourceFile ) )
{
_diskProvider . DeleteFile ( sourceFile ) ;
}
}
2017-09-19 04:21:26 +00:00
}
private void RemovePidFile ( )
{
if ( OsInfo . IsNotWindows )
{
_diskProvider . DeleteFile ( Path . Combine ( _appFolderInfo . AppDataFolder , "sonarr.pid" ) ) ;
}
}
2014-01-06 06:20:08 +00:00
}
}