private bool IsEpisodeFileMissing(Series series, EpisodeFile episodeFile)
        {
            var fullPath = Path.Combine(series.Path, episodeFile.RelativePath);

            return(!_diskProvider.FileExists(fullPath));
        }
示例#2
0
        private void MigrateAppDataFolder()
        {
            try
            {
                var oldDbFile = Path.Combine(_appFolderInfo.AppDataFolder, "nzbdrone.db");

                if (_startupContext.Args.ContainsKey(StartupContext.APPDATA))
                {
                    if (_diskProvider.FileExists(_appFolderInfo.GetDatabase()))
                    {
                        return;
                    }
                    if (!_diskProvider.FileExists(oldDbFile))
                    {
                        return;
                    }

                    MoveSqliteDatabase(oldDbFile, _appFolderInfo.GetDatabase());
                    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))
                {
                    MoveSqliteDatabase(oldDbFile, _appFolderInfo.GetDatabase());
                }

                // 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);
            }
        }
示例#3
0
        private bool CompareFiles(string sourceFile, string targetFile)
        {
            if (!_diskProvider.FileExists(sourceFile) || !_diskProvider.FileExists(targetFile))
            {
                return(false);
            }

            if (_diskProvider.GetFileSize(sourceFile) != _diskProvider.GetFileSize(targetFile))
            {
                return(false);
            }

            var sourceBuffer = new byte[64 * 1024];
            var targetBuffer = new byte[64 * 1024];

            using (var sourceStream = _diskProvider.OpenReadStream(sourceFile))
                using (var targetStream = _diskProvider.OpenReadStream(targetFile))
                {
                    while (true)
                    {
                        var sourceLength = sourceStream.Read(sourceBuffer, 0, sourceBuffer.Length);
                        var targetLength = targetStream.Read(targetBuffer, 0, targetBuffer.Length);

                        if (sourceLength != targetLength)
                        {
                            return(false);
                        }

                        if (sourceLength == 0)
                        {
                            return(true);
                        }

                        for (var i = 0; i < sourceLength; i++)
                        {
                            if (sourceBuffer[i] != targetBuffer[i])
                            {
                                return(false);
                            }
                        }
                    }
                }
        }
        public HealthCheck Check(IEvent message)
        {
            if (typeof(TrackImportFailedEvent).IsAssignableFrom(message.GetType()))
            {
                var failureMessage = (TrackImportFailedEvent)message;

                // if we can see the file exists but the import failed then likely a permissions issue
                if (failureMessage.TrackInfo != null)
                {
                    var trackPath = failureMessage.TrackInfo.Path;
                    if (_diskProvider.FileExists(trackPath))
                    {
                        return(new HealthCheck(GetType(), HealthCheckResult.Error, $"Gamearr can see but not access downloaded track {trackPath}.  Likely permissions error.", "#permissions-error"));
                    }
                    else
                    {
                        // If the file doesn't exist but TrackInfo is not null then the message is coming from
                        // ImportApprovedTracks and the file must have been removed part way through processing
                        return(new HealthCheck(GetType(), HealthCheckResult.Error, $"File {trackPath} was removed part way though procesing."));
                    }
                }

                // If the previous case did not match then the failure occured in DownloadedTracksImportService,
                // while trying to locate the files reported by the download client
                var client = _downloadClientProvider.GetDownloadClients().FirstOrDefault(x => x.Definition.Name == failureMessage.DownloadClient);
                try
                {
                    var status = client.GetStatus();
                    var dlpath = client?.GetItems().FirstOrDefault(x => x.DownloadId == failureMessage.DownloadId)?.OutputPath.FullPath;

                    // If dlpath is null then there's not much useful we can report.  Give a generic message so
                    // that the user realises something is wrong.
                    if (dlpath.IsNullOrWhiteSpace())
                    {
                        return(new HealthCheck(GetType(), HealthCheckResult.Error, $"Gamearr failed to import a track.  Check your logs for details."));
                    }

                    if (!dlpath.IsPathValid())
                    {
                        if (!status.IsLocalhost)
                        {
                            return(new HealthCheck(GetType(), HealthCheckResult.Error, $"Remote download client {client.Definition.Name} reported files in {dlpath} but this is not a valid {_osInfo.Name} path.  Review your remote path mappings and download client settings.", "#bad-remote-path-mapping"));
                        }
                        else if (_osInfo.IsDocker)
                        {
                            return(new HealthCheck(GetType(), HealthCheckResult.Error, $"You are using docker; download client {client.Definition.Name} reported files in {dlpath} but this is not a valid {_osInfo.Name} path.  Review your remote path mappings and download client settings.", "#docker-bad-remote-path-mapping"));
                        }
                        else
                        {
                            return(new HealthCheck(GetType(), HealthCheckResult.Error, $"Local download client {client.Definition.Name} reported files in {dlpath} but this is not a valid {_osInfo.Name} path.  Review your download client settings.", "#bad-download-client-settings"));
                        }
                    }

                    if (_diskProvider.FolderExists(dlpath))
                    {
                        return(new HealthCheck(GetType(), HealthCheckResult.Error, $"Gamearr can see but not access download directory {dlpath}.  Likely permissions error.", "#permissions-error"));
                    }

                    // if it's a remote client/docker, likely missing path mappings
                    if (_osInfo.IsDocker)
                    {
                        return(new HealthCheck(GetType(), HealthCheckResult.Error, $"You are using docker; download client {client.Definition.Name} reported files in {dlpath} but this directory does not appear to exist inside the container.  Review your remote path mappings and container volume settings.", "#docker-bad-remote-path-mapping"));
                    }
                    else if (!status.IsLocalhost)
                    {
                        return(new HealthCheck(GetType(), HealthCheckResult.Error, $"Remote download client {client.Definition.Name} reported files in {dlpath} but this directory does not appear to exist.  Likely missing remote path mapping.", "#bad-remote-path-mapping"));
                    }
                    else
                    {
                        // path mappings shouldn't be needed locally so probably a permissions issue
                        return(new HealthCheck(GetType(), HealthCheckResult.Error, $"Download client {client.Definition.Name} reported files in {dlpath} but Gamearr cannot see this directory.  You may need to adjust the folder's permissions.", "#permissions-error"));
                    }
                }
                catch (DownloadClientException ex)
                {
                    _logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name);
                }
                catch (Exception ex)
                {
                    _logger.Error(ex, "Unknown error occured in RemotePathMapping HealthCheck");
                }

                return(new HealthCheck(GetType()));
            }
            else
            {
                return(Check());
            }
        }
示例#5
0
        public MediaInfoModel GetMediaInfo(string filename)
        {
            if (!_diskProvider.FileExists(filename))
            {
                throw new FileNotFoundException("Media file does not exist: " + filename);
            }

            MediaInfo mediaInfo = null;

            try
            {
                mediaInfo = new MediaInfo();
                _logger.Debug("Getting media info from {0}", filename);

                mediaInfo.Option("ParseSpeed", "0.0");

                int open;

                using (var stream = _diskProvider.OpenReadStream(filename))
                {
                    open = mediaInfo.Open(stream);
                }

                if (open != 0)
                {
                    int audioRuntime;
                    int videoRuntime;
                    int generalRuntime;

                    //Runtime
                    Int32.TryParse(mediaInfo.Get(StreamKind.Video, 0, "PlayTime"), out videoRuntime);
                    Int32.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "PlayTime"), out audioRuntime);
                    Int32.TryParse(mediaInfo.Get(StreamKind.General, 0, "PlayTime"), out generalRuntime);

                    if (audioRuntime == 0 && videoRuntime == 0 && generalRuntime == 0)
                    {
                        mediaInfo.Option("ParseSpeed", "1.0");

                        using (var stream = _diskProvider.OpenReadStream(filename))
                        {
                            open = mediaInfo.Open(stream);
                        }
                    }
                }

                if (open != 0)
                {
                    int     width;
                    int     height;
                    int     videoBitRate;
                    int     audioBitRate;
                    int     audioRuntime;
                    int     videoRuntime;
                    int     generalRuntime;
                    int     streamCount;
                    int     audioChannels;
                    decimal videoFrameRate;

                    string subtitles = mediaInfo.Get(StreamKind.General, 0, "Text_Language_List");
                    string scanType  = mediaInfo.Get(StreamKind.Video, 0, "ScanType");
                    Int32.TryParse(mediaInfo.Get(StreamKind.Video, 0, "Width"), out width);
                    Int32.TryParse(mediaInfo.Get(StreamKind.Video, 0, "Height"), out height);
                    Int32.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitRate"), out videoBitRate);
                    Decimal.TryParse(mediaInfo.Get(StreamKind.Video, 0, "FrameRate"), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out videoFrameRate);

                    //Runtime
                    Int32.TryParse(mediaInfo.Get(StreamKind.Video, 0, "PlayTime"), out videoRuntime);
                    Int32.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "PlayTime"), out audioRuntime);
                    Int32.TryParse(mediaInfo.Get(StreamKind.General, 0, "PlayTime"), out generalRuntime);

                    string aBitRate = mediaInfo.Get(StreamKind.Audio, 0, "BitRate");
                    int    aBindex  = aBitRate.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase);
                    if (aBindex > 0)
                    {
                        aBitRate = aBitRate.Remove(aBindex);
                    }

                    Int32.TryParse(aBitRate, out audioBitRate);
                    Int32.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "StreamCount"), out streamCount);


                    string audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)");
                    int    aCindex          = audioChannelsStr.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase);
                    if (aCindex > 0)
                    {
                        audioChannelsStr = audioChannelsStr.Remove(aCindex);
                    }

                    string audioLanguages = mediaInfo.Get(StreamKind.General, 0, "Audio_Language_List");
                    string audioProfile   = mediaInfo.Get(StreamKind.Audio, 0, "Format_Profile");

                    int aPindex = audioProfile.IndexOf(" /", StringComparison.InvariantCultureIgnoreCase);
                    if (aPindex > 0)
                    {
                        audioProfile = audioProfile.Remove(aPindex);
                    }

                    Int32.TryParse(audioChannelsStr, out audioChannels);
                    var mediaInfoModel = new MediaInfoModel
                    {
                        VideoCodec       = mediaInfo.Get(StreamKind.Video, 0, "Codec/String"),
                        VideoBitrate     = videoBitRate,
                        Height           = height,
                        Width            = width,
                        AudioFormat      = mediaInfo.Get(StreamKind.Audio, 0, "Format"),
                        AudioBitrate     = audioBitRate,
                        RunTime          = GetBestRuntime(audioRuntime, videoRuntime, generalRuntime),
                        AudioStreamCount = streamCount,
                        AudioChannels    = audioChannels,
                        AudioProfile     = audioProfile.Trim(),
                        VideoFps         = videoFrameRate,
                        AudioLanguages   = audioLanguages,
                        Subtitles        = subtitles,
                        ScanType         = scanType
                    };

                    return(mediaInfoModel);
                }
                else
                {
                    _logger.Warn("Unable to open media info from file: " + filename);
                }
            }
            catch (DllNotFoundException ex)
            {
                _logger.ErrorException("mediainfo is required but was not found", ex);
            }
            catch (Exception ex)
            {
                _logger.ErrorException("Unable to parse media info from file: " + filename, ex);
            }
            finally
            {
                if (mediaInfo != null)
                {
                    mediaInfo.Close();
                }
            }

            return(null);
        }
示例#6
0
        public void DeleteFile(string path, string subfolder = "")
        {
            _logger.Debug("Attempting to send '{0}' to recycling bin", path);
            var recyclingBin = _configService.RecycleBin;

            if (string.IsNullOrWhiteSpace(recyclingBin))
            {
                _logger.Info("Recycling Bin has not been configured, deleting permanently. {0}", path);

                if (OsInfo.IsWindows)
                {
                    _logger.Debug(_diskProvider.GetFileAttributes(path));
                }

                _diskProvider.DeleteFile(path);
                _logger.Debug("File has been permanently deleted: {0}", path);
            }
            else
            {
                var fileInfo          = new FileInfo(path);
                var destinationFolder = Path.Combine(recyclingBin, subfolder);
                var destination       = Path.Combine(destinationFolder, fileInfo.Name);

                try
                {
                    _logger.Debug("Creating folder [0]", destinationFolder);
                    _diskProvider.CreateFolder(destinationFolder);
                }
                catch (IOException e)
                {
                    _logger.Error(e, "Unable to create the folder '{0}' in the recycling bin for the file '{1}'", destinationFolder, fileInfo.Name);
                    throw;
                }

                var index = 1;
                while (_diskProvider.FileExists(destination))
                {
                    index++;
                    if (fileInfo.Extension.IsNullOrWhiteSpace())
                    {
                        destination = Path.Combine(destinationFolder, fileInfo.Name + "_" + index);
                    }
                    else
                    {
                        destination = Path.Combine(destinationFolder, Path.GetFileNameWithoutExtension(fileInfo.Name) + "_" + index + fileInfo.Extension);
                    }
                }

                try
                {
                    _logger.Debug("Moving '{0}' to '{1}'", path, destination);
                    _diskTransferService.TransferFile(path, destination, TransferMode.Move);
                }
                catch (IOException e)
                {
                    _logger.Error(e, "Unable to move '{0}' to the recycling bin: '{1}'", path, destination);
                    throw;
                }

                SetLastWriteTime(destination, DateTime.UtcNow);

                _logger.Debug("File has been moved to the recycling bin: {0}", destination);
            }
        }
 private bool IsTrackFileMissing(Artist artist, TrackFile trackFile)
 {
     return(!_diskProvider.FileExists(trackFile.Path));
 }
        public BookFileMoveResult UpgradeBookFile(BookFile bookFile, LocalBook localBook, bool copyOnly = false)
        {
            var moveFileResult = new BookFileMoveResult();
            var existingFiles  = localBook.Book.BookFiles.Value;

            var rootFolderPath = _diskProvider.GetParentFolder(localBook.Author.Path);
            var rootFolder     = _rootFolderService.GetBestRootFolder(rootFolderPath);
            var isCalibre      = rootFolder.IsCalibreLibrary && rootFolder.CalibreSettings != null;

            var settings = rootFolder.CalibreSettings;

            // If there are existing book files and the root folder is missing, throw, so the old file isn't left behind during the import process.
            if (existingFiles.Any() && !_diskProvider.FolderExists(rootFolderPath))
            {
                throw new RootFolderNotFoundException($"Root folder '{rootFolderPath}' was not found.");
            }

            foreach (var file in existingFiles)
            {
                var bookFilePath = file.Path;
                var subfolder    = rootFolderPath.GetRelativePath(_diskProvider.GetParentFolder(bookFilePath));

                bookFile.CalibreId = file.CalibreId;

                if (_diskProvider.FileExists(bookFilePath))
                {
                    _logger.Debug("Removing existing book file: {0} CalibreId: {1}", file, file.CalibreId);

                    if (!isCalibre)
                    {
                        _recycleBinProvider.DeleteFile(bookFilePath, subfolder);
                    }
                    else
                    {
                        var existing        = _calibre.GetBook(file.CalibreId, settings);
                        var existingFormats = existing.Formats.Keys;
                        _logger.Debug($"Removing existing formats {existingFormats.ConcatToString()} from calibre");
                        _calibre.RemoveFormats(file.CalibreId, existingFormats, settings);
                    }
                }

                moveFileResult.OldFiles.Add(file);
                _mediaFileService.Delete(file, DeleteMediaFileReason.Upgrade);
            }

            if (!isCalibre)
            {
                if (copyOnly)
                {
                    moveFileResult.BookFile = _bookFileMover.CopyBookFile(bookFile, localBook);
                }
                else
                {
                    moveFileResult.BookFile = _bookFileMover.MoveBookFile(bookFile, localBook);
                }

                _audioTagService.WriteTags(bookFile, true);
            }
            else
            {
                var source = bookFile.Path;

                moveFileResult.BookFile = CalibreAddAndConvert(bookFile, settings);

                if (!copyOnly)
                {
                    _diskProvider.DeleteFile(source);
                }
            }

            return(moveFileResult);
        }
示例#9
0
 private void ClearTargetPath(string targetPath, bool overwrite)
 {
     if (_diskProvider.FileExists(targetPath))
     {
         if (overwrite)
         {
             _diskProvider.DeleteFile(targetPath);
         }
         else
         {
             throw new IOException(string.Format("Destination already exists [{0}]", targetPath));
         }
     }
 }
示例#10
0
        private MovieFile TransferFile(MovieFile movieFile, Movie movie, string destinationFilePath, TransferMode mode)
        {
            Ensure.That(movieFile, () => movieFile).IsNotNull();
            Ensure.That(movie, () => movie).IsNotNull();
            Ensure.That(destinationFilePath, () => destinationFilePath).IsValidPath();

            var movieFilePath = movieFile.Path ?? Path.Combine(movie.Path, movieFile.RelativePath);

            if (!_diskProvider.FileExists(movieFilePath))
            {
                throw new FileNotFoundException("Movie file path does not exist", movieFilePath);
            }

            if (movieFilePath == destinationFilePath)
            {
                throw new SameFilenameException("File not moved, source and destination are the same", movieFilePath);
            }

            _diskTransferService.TransferFile(movieFilePath, destinationFilePath, mode);

            var oldMoviePath = movie.Path;

            var newMoviePath = new OsPath(destinationFilePath).Directory.FullPath.TrimEnd(Path.DirectorySeparatorChar);

            movie.Path = newMoviePath; //We update it when everything went well!

            movieFile.RelativePath = movie.Path.GetRelativePath(destinationFilePath);

            _updateMovieFileService.ChangeFileDateForFile(movieFile, movie);

            try
            {
                _mediaFileAttributeService.SetFolderLastWriteTime(movie.Path, movieFile.DateAdded);
            }

            catch (Exception ex)
            {
                _logger.Warn(ex, "Unable to set last write time");
            }

            _mediaFileAttributeService.SetFilePermissions(destinationFilePath);

            if (oldMoviePath != newMoviePath && _diskProvider.FolderExists(oldMoviePath))
            {
                //Let's move the old files before deleting the old folder. We could just do move folder, but the main file (movie file) is already moved, so eh.
                var files = _diskProvider.GetFiles(oldMoviePath, SearchOption.AllDirectories);

                foreach (var file in files)
                {
                    try
                    {
                        var destFile = Path.Combine(newMoviePath, oldMoviePath.GetRelativePath(file));
                        _diskProvider.EnsureFolder(Path.GetDirectoryName(destFile));
                        _diskProvider.MoveFile(file, destFile);
                    }
                    catch (Exception e)
                    {
                        _logger.Warn(e, "Error while trying to move extra file {0} to new folder. Maybe it already exists? (Manual cleanup necessary!).", oldMoviePath.GetRelativePath(file));
                    }
                }

                if (_diskProvider.GetFiles(oldMoviePath, SearchOption.AllDirectories).Count() == 0)
                {
                    _recycleBinProvider.DeleteFolder(oldMoviePath);
                }
            }

            //Only update the movie path if we were successfull!
            if (oldMoviePath != newMoviePath)
            {
                _movieService.UpdateMovie(movie);
            }

            return(movieFile);
        }
示例#11
0
        public MediaInfoModel GetMediaInfo(string filename)
        {
            if (!_diskProvider.FileExists(filename))
            {
                throw new FileNotFoundException("Media file does not exist: " + filename);
            }

            // TODO: Cache media info by path, mtime and length so we don't need to read files multiple times
            try
            {
                _logger.Debug("Getting media info from {0}", filename);
                var ffprobeOutput = FFProbe.GetStreamJson(filename, ffOptions: new FFOptions {
                    ExtraArguments = "-probesize 50000000"
                });

                var analysis = FFProbe.AnalyseStreamJson(ffprobeOutput);

                if (analysis.PrimaryAudioStream?.ChannelLayout.IsNullOrWhiteSpace() ?? true)
                {
                    ffprobeOutput = FFProbe.GetStreamJson(filename, ffOptions: new FFOptions {
                        ExtraArguments = "-probesize 150000000 -analyzeduration 150000000"
                    });
                    analysis = FFProbe.AnalyseStreamJson(ffprobeOutput);
                }

                var mediaInfoModel = new MediaInfoModel();
                mediaInfoModel.ContainerFormat              = analysis.Format.FormatName;
                mediaInfoModel.VideoFormat                  = analysis.PrimaryVideoStream?.CodecName;
                mediaInfoModel.VideoCodecID                 = analysis.PrimaryVideoStream?.CodecTagString;
                mediaInfoModel.VideoProfile                 = analysis.PrimaryVideoStream?.Profile;
                mediaInfoModel.VideoBitrate                 = analysis.PrimaryVideoStream?.BitRate ?? 0;
                mediaInfoModel.VideoMultiViewCount          = 1;
                mediaInfoModel.VideoBitDepth                = GetPixelFormat(analysis.PrimaryVideoStream?.PixelFormat)?.Components.Min(x => x.BitDepth) ?? 8;
                mediaInfoModel.VideoColourPrimaries         = analysis.PrimaryVideoStream?.ColorPrimaries;
                mediaInfoModel.VideoTransferCharacteristics = analysis.PrimaryVideoStream?.ColorTransfer;
                mediaInfoModel.DoviConfigurationRecord      = analysis.PrimaryVideoStream?.SideDataList?.Find(x => x.GetType().Name == nameof(DoviConfigurationRecordSideData)) as DoviConfigurationRecordSideData;
                mediaInfoModel.Height                = analysis.PrimaryVideoStream?.Height ?? 0;
                mediaInfoModel.Width                 = analysis.PrimaryVideoStream?.Width ?? 0;
                mediaInfoModel.AudioFormat           = analysis.PrimaryAudioStream?.CodecName;
                mediaInfoModel.AudioCodecID          = analysis.PrimaryAudioStream?.CodecTagString;
                mediaInfoModel.AudioProfile          = analysis.PrimaryAudioStream?.Profile;
                mediaInfoModel.AudioBitrate          = analysis.PrimaryAudioStream?.BitRate ?? 0;
                mediaInfoModel.RunTime               = GetBestRuntime(analysis.PrimaryAudioStream?.Duration, analysis.PrimaryVideoStream?.Duration, analysis.Format.Duration);
                mediaInfoModel.AudioStreamCount      = analysis.AudioStreams.Count;
                mediaInfoModel.AudioChannels         = analysis.PrimaryAudioStream?.Channels ?? 0;
                mediaInfoModel.AudioChannelPositions = analysis.PrimaryAudioStream?.ChannelLayout;
                mediaInfoModel.VideoFps              = analysis.PrimaryVideoStream?.FrameRate ?? 0;
                mediaInfoModel.AudioLanguages        = analysis.AudioStreams?.Select(x => x.Language)
                                                       .Where(l => l.IsNotNullOrWhiteSpace())
                                                       .ToList();
                mediaInfoModel.Subtitles = analysis.SubtitleStreams?.Select(x => x.Language)
                                           .Where(l => l.IsNotNullOrWhiteSpace())
                                           .ToList();
                mediaInfoModel.ScanType       = "Progressive";
                mediaInfoModel.RawStreamData  = ffprobeOutput;
                mediaInfoModel.SchemaRevision = CURRENT_MEDIA_INFO_SCHEMA_REVISION;

                FFProbeFrames frames = null;

                // if it looks like PQ10 or similar HDR, do a frame analysis to figure out which type it is
                if (PqTransferFunctions.Contains(mediaInfoModel.VideoTransferCharacteristics))
                {
                    var frameOutput = FFProbe.GetFrameJson(filename, ffOptions: new () { ExtraArguments = "-read_intervals \"%+#1\" -select_streams v" });
                    mediaInfoModel.RawFrameData = frameOutput;

                    frames = FFProbe.AnalyseFrameJson(frameOutput);
                }

                var streamSideData = analysis.PrimaryVideoStream?.SideDataList ?? new ();
                var framesSideData = frames?.Frames?.Count > 0 ? frames?.Frames[0]?.SideDataList ?? new () : new ();

                var sideData = streamSideData.Concat(framesSideData).ToList();
                mediaInfoModel.VideoHdrFormat = GetHdrFormat(mediaInfoModel.VideoBitDepth, mediaInfoModel.VideoColourPrimaries, mediaInfoModel.VideoTransferCharacteristics, sideData);

                return(mediaInfoModel);
            }
            catch (Exception ex)
            {
                _logger.Error(ex, "Unable to parse media info from file: {0}", filename);
            }

            return(null);
        }
        private EpisodeFile MoveFile(EpisodeFile episodeFile, Series series, List <Episode> episodes, string destinationFilename)
        {
            Ensure.That(episodeFile, () => episodeFile).IsNotNull();
            Ensure.That(series, () => series).IsNotNull();
            Ensure.That(destinationFilename, () => destinationFilename).IsValidPath();

            if (!_diskProvider.FileExists(episodeFile.Path))
            {
                throw new FileNotFoundException("Episode file path does not exist", episodeFile.Path);
            }

            if (episodeFile.Path.PathEquals(destinationFilename))
            {
                throw new SameFilenameException("File not moved, source and destination are the same", episodeFile.Path);
            }

            var directoryName = new FileInfo(destinationFilename).DirectoryName;

            if (!_diskProvider.FolderExists(directoryName))
            {
                try
                {
                    _diskProvider.CreateFolder(directoryName);
                }
                catch (IOException ex)
                {
                    _logger.ErrorException("Unable to create directory: " + directoryName, ex);
                }

                SetFolderPermissions(directoryName);

                if (!directoryName.PathEquals(series.Path))
                {
                    SetFolderPermissions(series.Path);
                }
            }

            _logger.Debug("Moving [{0}] > [{1}]", episodeFile.Path, destinationFilename);
            _diskProvider.MoveFile(episodeFile.Path, destinationFilename);
            episodeFile.Path = destinationFilename;

            _updateEpisodeFileService.ChangeFileDateForFile(episodeFile, series, episodes);

            try
            {
                _logger.Debug("Setting last write time on series folder: {0}", series.Path);
                _diskProvider.FolderSetLastWriteTimeUtc(series.Path, episodeFile.DateAdded);

                if (series.SeasonFolder)
                {
                    var seasonFolder = Path.GetDirectoryName(destinationFilename);

                    _logger.Debug("Setting last write time on season folder: {0}", seasonFolder);
                    _diskProvider.FolderSetLastWriteTimeUtc(seasonFolder, episodeFile.DateAdded);
                }
            }

            catch (Exception ex)
            {
                _logger.WarnException("Unable to set last write time", ex);
            }

            //We should only run this on Windows
            if (OsInfo.IsWindows)
            {
                //Wrapped in Try/Catch to prevent this from causing issues with remote NAS boxes, the move worked, which is more important.
                try
                {
                    _diskProvider.InheritFolderPermissions(destinationFilename);
                }
                catch (Exception ex)
                {
                    if (ex is UnauthorizedAccessException || ex is InvalidOperationException)
                    {
                        _logger.Debug("Unable to apply folder permissions to: ", destinationFilename);
                        _logger.DebugException(ex.Message, ex);
                    }

                    else
                    {
                        throw;
                    }
                }
            }

            else
            {
                SetPermissions(destinationFilename, _configService.FileChmod);
            }

            return(episodeFile);
        }
示例#13
0
        private bool InstallUpdate(UpdatePackage updatePackage)
        {
            EnsureAppDataSafety();

            if (OsInfo.IsWindows || _configFileProvider.UpdateMechanism != UpdateMechanism.Script)
            {
                var startupFolder = _appFolderInfo.StartUpFolder;
                var uiFolder      = Path.Combine(startupFolder, "UI");

                if (!_diskProvider.FolderWritable(startupFolder))
                {
                    throw new UpdateFolderNotWritableException("Cannot install update because startup folder '{0}' is not writable by the user '{1}'.", startupFolder, Environment.UserName);
                }

                if (!_diskProvider.FolderWritable(uiFolder))
                {
                    throw new UpdateFolderNotWritableException("Cannot install update because UI folder '{0}' is not writable by the user '{1}'.", uiFolder, Environment.UserName);
                }
            }

            if (_appFolderInfo.StartUpFolder.EndsWith("_output"))
            {
                _logger.ProgressDebug("Running in developer environment, not updating.");
                return(false);
            }

            var updateSandboxFolder = _appFolderInfo.GetUpdateSandboxFolder();

            var packageDestination = Path.Combine(updateSandboxFolder, updatePackage.FileName);

            if (_diskProvider.FolderExists(updateSandboxFolder))
            {
                _logger.Info("Deleting old update files");
                _diskProvider.DeleteFolder(updateSandboxFolder, true);
            }

            _logger.ProgressInfo("Downloading update {0}", updatePackage.Version);
            _logger.Debug("Downloading update package from [{0}] to [{1}]", updatePackage.Url, packageDestination);
            _httpClient.DownloadFile(updatePackage.Url, packageDestination);

            _logger.ProgressInfo("Verifying update package");

            if (!_updateVerifier.Verify(updatePackage, packageDestination))
            {
                _logger.Error("Update package is invalid");
                throw new UpdateVerificationFailedException("Update file '{0}' is invalid", packageDestination);
            }

            _logger.Info("Update package verified successfully");

            _logger.ProgressInfo("Extracting Update package");
            _archiveService.Extract(packageDestination, updateSandboxFolder);
            _logger.Info("Update package extracted successfully");

            EnsureValidBranch(updatePackage);

            _backupService.Backup(BackupType.Update);

            if (OsInfo.IsNotWindows && _configFileProvider.UpdateMechanism == UpdateMechanism.Script)
            {
                InstallUpdateWithScript(updateSandboxFolder);
                return(true);
            }

            _logger.Info("Preparing client");
            _diskTransferService.TransferFolder(_appFolderInfo.GetUpdateClientFolder(), updateSandboxFolder, TransferMode.Move);

            var updateClientExePath = _appFolderInfo.GetUpdateClientExePath(updatePackage.Runtime);

            if (!_diskProvider.FileExists(updateClientExePath))
            {
                _logger.Warn("Update client {0} does not exist, aborting update.", updateClientExePath);
                return(false);
            }

            // Set executable flag on update app
            if (OsInfo.IsOsx || (OsInfo.IsLinux && PlatformInfo.IsNetCore))
            {
                _diskProvider.SetFilePermissions(updateClientExePath, "755", null);
            }

            _logger.Info("Starting update client {0}", updateClientExePath);
            _logger.ProgressInfo("Lidarr will restart shortly.");

            _processProvider.Start(updateClientExePath, GetUpdaterArgs(updateSandboxFolder));

            return(true);
        }
示例#14
0
        public MediaInfoModel GetMediaInfo(string filename)
        {
            if (!_diskProvider.FileExists(filename))
            {
                throw new FileNotFoundException("Media file does not exist: " + filename);
            }

            MediaInfo mediaInfo = null;

            // TODO: Cache media info by path, mtime and length so we don't need to read files multiple times
            try
            {
                mediaInfo = new MediaInfo();
                _logger.Debug("Getting media info from {0}", filename);

                if (filename.ToLower().EndsWith(".ts"))
                {
                    // For .ts files we often have to scan more of the file to get all the info we need
                    mediaInfo.Option("ParseSpeed", "0.3");
                }
                else
                {
                    mediaInfo.Option("ParseSpeed", "0.0");
                }

                int open;

                using (var stream = _diskProvider.OpenReadStream(filename))
                {
                    open = mediaInfo.Open(stream);
                }

                if (open != 0)
                {
                    int audioRuntime;
                    int videoRuntime;
                    int generalRuntime;

                    // Runtime
                    int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "PlayTime"), out videoRuntime);
                    int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "PlayTime"), out audioRuntime);
                    int.TryParse(mediaInfo.Get(StreamKind.General, 0, "PlayTime"), out generalRuntime);

                    // Audio Channels
                    var audioChannelsStr      = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
                    var audioChannelPositions = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions/String2");
                    int.TryParse(audioChannelsStr, out var audioChannels);

                    if (audioRuntime == 0 && videoRuntime == 0 && generalRuntime == 0)
                    {
                        // No runtime, ask mediainfo to scan the whole file
                        mediaInfo.Option("ParseSpeed", "1.0");

                        using (var stream = _diskProvider.OpenReadStream(filename))
                        {
                            open = mediaInfo.Open(stream);
                        }
                    }
                    else if (audioChannels > 2 && audioChannelPositions.IsNullOrWhiteSpace())
                    {
                        // Some files with DTS don't have ChannelPositions unless more of the file is scanned
                        mediaInfo.Option("ParseSpeed", "0.3");

                        using (var stream = _diskProvider.OpenReadStream(filename))
                        {
                            open = mediaInfo.Open(stream);
                        }
                    }
                }

                if (open != 0)
                {
                    int     width;
                    int     height;
                    int     videoBitRate;
                    int     audioBitRate;
                    int     audioRuntime;
                    int     videoRuntime;
                    int     generalRuntime;
                    int     streamCount;
                    int     audioChannels;
                    int     audioChannelsOrig;
                    int     videoBitDepth;
                    decimal videoFrameRate;
                    int     videoMultiViewCount;

                    string subtitles = mediaInfo.Get(StreamKind.General, 0, "Text_Language_List");
                    string scanType  = mediaInfo.Get(StreamKind.Video, 0, "ScanType");
                    int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "Width"), out width);
                    int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "Height"), out height);
                    int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitRate"), out videoBitRate);
                    if (videoBitRate <= 0)
                    {
                        int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitRate_Nominal"), out videoBitRate);
                    }

                    decimal.TryParse(mediaInfo.Get(StreamKind.Video, 0, "FrameRate"), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out videoFrameRate);
                    int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "BitDepth"), out videoBitDepth);
                    int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "MultiView_Count"), out videoMultiViewCount);

                    //Runtime
                    int.TryParse(mediaInfo.Get(StreamKind.Video, 0, "PlayTime"), out videoRuntime);
                    int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "PlayTime"), out audioRuntime);
                    int.TryParse(mediaInfo.Get(StreamKind.General, 0, "PlayTime"), out generalRuntime);

                    string aBitRate = mediaInfo.Get(StreamKind.Audio, 0, "BitRate").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();

                    int.TryParse(aBitRate, out audioBitRate);
                    int.TryParse(mediaInfo.Get(StreamKind.Audio, 0, "StreamCount"), out streamCount);

                    string audioChannelsStr = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
                    int.TryParse(audioChannelsStr, out audioChannels);

                    string audioChannelsStrOrig = mediaInfo.Get(StreamKind.Audio, 0, "Channel(s)_Original").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
                    int.TryParse(audioChannelsStrOrig, out audioChannelsOrig);

                    var audioChannelPositionsText     = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions");
                    var audioChannelPositionsTextOrig = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions_Original");
                    var audioChannelPositions         = mediaInfo.Get(StreamKind.Audio, 0, "ChannelPositions/String2");

                    string audioLanguages = mediaInfo.Get(StreamKind.General, 0, "Audio_Language_List");

                    string videoProfile = mediaInfo.Get(StreamKind.Video, 0, "Format_Profile").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();
                    string audioProfile = mediaInfo.Get(StreamKind.Audio, 0, "Format_Profile").Split(new string[] { " /" }, StringSplitOptions.None)[0].Trim();

                    var mediaInfoModel = new MediaInfoModel
                    {
                        ContainerFormat              = mediaInfo.Get(StreamKind.General, 0, "Format"),
                        VideoFormat                  = mediaInfo.Get(StreamKind.Video, 0, "Format"),
                        VideoCodecID                 = mediaInfo.Get(StreamKind.Video, 0, "CodecID"),
                        VideoProfile                 = videoProfile,
                        VideoCodecLibrary            = mediaInfo.Get(StreamKind.Video, 0, "Encoded_Library"),
                        VideoBitrate                 = videoBitRate,
                        VideoBitDepth                = videoBitDepth,
                        VideoMultiViewCount          = videoMultiViewCount,
                        VideoColourPrimaries         = mediaInfo.Get(StreamKind.Video, 0, "colour_primaries"),
                        VideoTransferCharacteristics = mediaInfo.Get(StreamKind.Video, 0, "transfer_characteristics"),
                        Height                             = height,
                        Width                              = width,
                        AudioFormat                        = mediaInfo.Get(StreamKind.Audio, 0, "Format"),
                        AudioCodecID                       = mediaInfo.Get(StreamKind.Audio, 0, "CodecID"),
                        AudioProfile                       = audioProfile,
                        AudioCodecLibrary                  = mediaInfo.Get(StreamKind.Audio, 0, "Encoded_Library"),
                        AudioAdditionalFeatures            = mediaInfo.Get(StreamKind.Audio, 0, "Format_AdditionalFeatures"),
                        AudioBitrate                       = audioBitRate,
                        RunTime                            = GetBestRuntime(audioRuntime, videoRuntime, generalRuntime),
                        AudioStreamCount                   = streamCount,
                        AudioChannelsContainer             = audioChannels,
                        AudioChannelsStream                = audioChannelsOrig,
                        AudioChannelPositions              = audioChannelPositions,
                        AudioChannelPositionsTextContainer = audioChannelPositionsText,
                        AudioChannelPositionsTextStream    = audioChannelPositionsTextOrig,
                        VideoFps                           = videoFrameRate,
                        AudioLanguages                     = audioLanguages,
                        Subtitles                          = subtitles,
                        ScanType                           = scanType,
                        SchemaRevision                     = CURRENT_MEDIA_INFO_SCHEMA_REVISION
                    };

                    return(mediaInfoModel);
                }
                else
                {
                    _logger.Warn("Unable to open media info from file: " + filename);
                }
            }
            catch (DllNotFoundException ex)
            {
                _logger.Error(ex, "mediainfo is required but was not found");
            }
            catch (Exception ex)
            {
                _logger.Error(ex, "Unable to parse media info from file: {0}", filename);
            }
            finally
            {
                mediaInfo?.Close();
            }

            return(null);
        }
示例#15
0
        public void CheckForCompletedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List <History.History> grabbedHistory, List <History.History> importedHistory)
        {
            if (!_configService.EnableCompletedDownloadHandling)
            {
                return;
            }

            if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Completed && trackedDownload.State == TrackedDownloadState.Downloading)
            {
                var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId);

                if (!grabbedItems.Any() && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
                {
                    UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Download wasn't grabbed by drone or not in a category, ignoring download.");
                    return;
                }

                var importedItems = GetHistoryItems(importedHistory, trackedDownload.DownloadItem.DownloadClientId);

                if (importedItems.Any())
                {
                    trackedDownload.State = TrackedDownloadState.Imported;

                    UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Already added to history as imported.");
                }
                else
                {
                    string downloadedEpisodesFolder = _configService.DownloadedEpisodesFolder;
                    string downloadItemOutputPath   = trackedDownload.DownloadItem.OutputPath;
                    if (downloadItemOutputPath.IsNullOrWhiteSpace())
                    {
                        UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download doesn't contain intermediate path, ignoring download.");
                        return;
                    }

                    if (!downloadedEpisodesFolder.IsNullOrWhiteSpace() && (downloadedEpisodesFolder.PathEquals(downloadItemOutputPath) || downloadedEpisodesFolder.IsParentPath(downloadItemOutputPath)))
                    {
                        UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Intermediate Download path inside drone factory, ignoring download.");
                        return;
                    }

                    if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath))
                    {
                        var importResults = _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem);

                        ProcessImportResults(trackedDownload, importResults);
                    }
                    else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath))
                    {
                        var importResults = _downloadedEpisodesImportService.ProcessFile(new FileInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem);

                        ProcessImportResults(trackedDownload, importResults);
                    }
                    else
                    {
                        if (grabbedItems.Any())
                        {
                            var episodeIds = trackedDownload.DownloadItem.RemoteEpisode.Episodes.Select(v => v.Id).ToList();

                            // Check if we can associate it with a previous drone factory import.
                            importedItems = importedHistory.Where(v => v.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID) == null &&
                                                                  episodeIds.Contains(v.EpisodeId) &&
                                                                  v.Data.GetValueOrDefault("droppedPath") != null &&
                                                                  new FileInfo(v.Data["droppedPath"]).Directory.Name == grabbedItems.First().SourceTitle
                                                                  ).ToList();
                            if (importedItems.Count == 1)
                            {
                                var importedFile = new FileInfo(importedItems.First().Data["droppedPath"]);

                                if (importedFile.Directory.Name == grabbedItems.First().SourceTitle)
                                {
                                    trackedDownload.State = TrackedDownloadState.Imported;

                                    importedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT]    = grabbedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT];
                                    importedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID] = grabbedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID];
                                    _historyService.UpdateHistoryData(importedItems.First().Id, importedItems.First().Data);

                                    UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Intermediate Download path does not exist, but found probable drone factory ImportEvent.");
                                    return;
                                }
                            }
                        }

                        UpdateStatusMessage(trackedDownload, LogLevel.Error, "Intermediate Download path does not exist: {0}", trackedDownload.DownloadItem.OutputPath);
                        return;
                    }
                }
            }

            if (_configService.RemoveCompletedDownloads && trackedDownload.State == TrackedDownloadState.Imported && !trackedDownload.DownloadItem.IsReadOnly)
            {
                try
                {
                    _logger.Debug("[{0}] Removing completed download from history.", trackedDownload.DownloadItem.Title);
                    downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId);

                    if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath))
                    {
                        _logger.Debug("Removing completed download directory: {0}", trackedDownload.DownloadItem.OutputPath);
                        _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath, true);
                    }
                    else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath))
                    {
                        _logger.Debug("Removing completed download file: {0}", trackedDownload.DownloadItem.OutputPath);
                        _diskProvider.DeleteFile(trackedDownload.DownloadItem.OutputPath);
                    }

                    trackedDownload.State = TrackedDownloadState.Removed;
                }
                catch (NotSupportedException)
                {
                    UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Removing item not supported by your download client.");
                }
            }
        }
示例#16
0
        public void Start(string installationFolder, int processId)
        {
            _logger.Info("Installation Folder: {0}", installationFolder);
            _logger.Info("Updating Sonarr from version {0} to version {1}", _detectExistingVersion.GetExistingVersion(installationFolder), BuildInfo.Version);

            Verify(installationFolder, processId);

            var appType = _detectApplicationType.GetAppType();

            _processProvider.FindProcessByName(ProcessProvider.SONARR_CONSOLE_PROCESS_NAME);
            _processProvider.FindProcessByName(ProcessProvider.SONARR_PROCESS_NAME);

            if (OsInfo.IsWindows)
            {
                _terminateNzbDrone.Terminate(processId);
            }

            try
            {
                _backupAndRestore.Backup(installationFolder);
                _backupAppData.Backup();

                if (OsInfo.IsWindows)
                {
                    if (_processProvider.Exists(ProcessProvider.SONARR_CONSOLE_PROCESS_NAME) || _processProvider.Exists(ProcessProvider.SONARR_PROCESS_NAME))
                    {
                        _logger.Error("Sonarr was restarted prematurely by external process.");
                        return;
                    }
                }

                try
                {
                    _logger.Info("Copying new files to target folder");
                    _diskTransferService.MirrorFolder(_appFolderInfo.GetUpdatePackageFolder(), installationFolder);

                    // Handle OSX package update and set executable flag on Sonarr app
                    if (OsInfo.IsOsx)
                    {
                        var shimPath     = Path.Combine(installationFolder, "Sonarr");
                        var realShimPath = Path.Combine(installationFolder, "../Sonarr");

                        if (installationFolder.EndsWith("/MacOS/bin") && _diskProvider.FileExists(realShimPath))
                        {
                            // New MacOS App stores Sonarr binaries in MacOS/bin and has a shim in MacOS
                            // Delete the shim in the downloaded update, we shouldn't update the shim unnecessarily
                            _diskProvider.DeleteFile(shimPath);
                        }
                        else
                        {
                            // Old MacOS App stores Sonarr binaries in MacOS together with shell script
                            // Make shim executable
                            _diskProvider.SetPermissions(shimPath, "0755");
                        }
                    }
                }
                catch (Exception e)
                {
                    _logger.Error(e, "Failed to copy upgrade package to target folder.");
                    _backupAndRestore.Restore(installationFolder);
                    throw;
                }
            }
            finally
            {
                if (OsInfo.IsWindows)
                {
                    _startNzbDrone.Start(appType, installationFolder);
                }
                else
                {
                    _terminateNzbDrone.Terminate(processId);

                    _logger.Info("Waiting for external auto-restart.");
                    for (int i = 0; i < 5; i++)
                    {
                        System.Threading.Thread.Sleep(1000);

                        if (_processProvider.Exists(ProcessProvider.SONARR_PROCESS_NAME))
                        {
                            _logger.Info("Sonarr was restarted by external process.");
                            break;
                        }
                    }

                    if (!_processProvider.Exists(ProcessProvider.SONARR_PROCESS_NAME))
                    {
                        _startNzbDrone.Start(appType, installationFolder);
                    }
                }
            }
        }