1074 lines
34 KiB
C#
1074 lines
34 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Diagnostics;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Threading;
|
||
|
||
namespace QobuzWatchdog
|
||
{
|
||
internal class Program
|
||
{
|
||
private static string _ripPath;
|
||
private static string _projectRoot;
|
||
private static string _downloadsPath;
|
||
|
||
// Количество попыток запуска rip.exe для одной ссылки за один проход.
|
||
private const int MaxRunsPerUrl = 10;
|
||
|
||
// Максимальное количество полных проходов по незавершённым ссылкам.
|
||
private const int MaxRounds = 20;
|
||
|
||
// Через сколько секунд без вывода считать процесс зависшим.
|
||
private const int IdleLimitSeconds = 60;
|
||
|
||
// Задержка перед повторным запуском.
|
||
private const int RestartDelaySeconds = 3;
|
||
|
||
private static DateTime _lastOutputTime;
|
||
private static readonly object OutputLock = new object();
|
||
|
||
private static string _completedPath;
|
||
private static string _failedPath;
|
||
|
||
static void Main(string[] args)
|
||
{
|
||
Console.OutputEncoding = Encoding.UTF8;
|
||
|
||
string exeFolder = AppDomain.CurrentDomain.BaseDirectory;
|
||
|
||
_projectRoot = FindProjectRoot(exeFolder);
|
||
_downloadsPath = Path.Combine(
|
||
_projectRoot,
|
||
"downloads"
|
||
);
|
||
|
||
string dataFolder = Path.Combine(
|
||
_projectRoot,
|
||
"data"
|
||
);
|
||
|
||
Directory.CreateDirectory(_downloadsPath);
|
||
Directory.CreateDirectory(dataFolder);
|
||
|
||
string urlsPath = Path.Combine(
|
||
dataFolder,
|
||
"urls.txt"
|
||
);
|
||
|
||
_completedPath = Path.Combine(
|
||
dataFolder,
|
||
"completed_urls.txt"
|
||
);
|
||
|
||
_failedPath = Path.Combine(
|
||
dataFolder,
|
||
"failed_urls.txt"
|
||
);
|
||
|
||
_ripPath = FindRipExecutable(_projectRoot);
|
||
|
||
if (string.IsNullOrWhiteSpace(_ripPath))
|
||
{
|
||
Console.WriteLine("Не найден Streamrip (rip.exe).");
|
||
Console.WriteLine();
|
||
Console.WriteLine("Проверены:");
|
||
Console.WriteLine(
|
||
Path.Combine(_projectRoot, "tools", "rip.exe")
|
||
);
|
||
Console.WriteLine("и системная переменная PATH.");
|
||
WaitForExit();
|
||
return;
|
||
}
|
||
|
||
Console.WriteLine("Streamrip:");
|
||
Console.WriteLine(_ripPath);
|
||
Console.WriteLine();
|
||
Console.WriteLine("Папка загрузок:");
|
||
Console.WriteLine(_downloadsPath);
|
||
Console.WriteLine();
|
||
|
||
if (!ConfigureStreamripDownloadFolder(_downloadsPath))
|
||
{
|
||
WaitForExit();
|
||
return;
|
||
}
|
||
|
||
if (!File.Exists(urlsPath))
|
||
{
|
||
File.WriteAllText(
|
||
urlsPath,
|
||
"# Каждая ссылка должна находиться на отдельной строке." +
|
||
Environment.NewLine,
|
||
Encoding.UTF8
|
||
);
|
||
|
||
Console.WriteLine("Создан файл:");
|
||
Console.WriteLine(urlsPath);
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Добавь в него ссылки и снова запусти программу."
|
||
);
|
||
|
||
WaitForExit();
|
||
return;
|
||
}
|
||
|
||
List<string> allUrls = ReadUrls(urlsPath);
|
||
|
||
if (allUrls.Count == 0)
|
||
{
|
||
Console.WriteLine("В urls.txt нет ссылок.");
|
||
WaitForExit();
|
||
return;
|
||
}
|
||
|
||
var allUrlsSet = new HashSet<string>(
|
||
allUrls,
|
||
StringComparer.OrdinalIgnoreCase
|
||
);
|
||
|
||
HashSet<string> completedUrls =
|
||
ReadUrlSet(_completedPath);
|
||
|
||
// Удаляем из completed_urls.txt старые ссылки,
|
||
// которых больше нет в urls.txt.
|
||
completedUrls.RemoveWhere(
|
||
completedUrl => !allUrlsSet.Contains(completedUrl)
|
||
);
|
||
|
||
SaveCompletedUrls(completedUrls);
|
||
|
||
List<string> pendingUrls = allUrls
|
||
.Where(url => !completedUrls.Contains(url))
|
||
.ToList();
|
||
|
||
Console.WriteLine("=======================================");
|
||
Console.WriteLine(" НАЧАЛЬНОЕ СОСТОЯНИЕ");
|
||
Console.WriteLine("=======================================");
|
||
Console.WriteLine("Всего ссылок: " + allUrls.Count);
|
||
Console.WriteLine("Уже завершено: " + completedUrls.Count);
|
||
Console.WriteLine("Осталось скачать: " + pendingUrls.Count);
|
||
Console.WriteLine("=======================================");
|
||
|
||
if (pendingUrls.Count == 0)
|
||
{
|
||
if (File.Exists(_failedPath))
|
||
{
|
||
File.Delete(_failedPath);
|
||
}
|
||
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Все ссылки из urls.txt уже обработаны."
|
||
);
|
||
|
||
WaitForExit();
|
||
return;
|
||
}
|
||
|
||
int round = 1;
|
||
|
||
while (
|
||
pendingUrls.Count > 0 &&
|
||
round <= MaxRounds)
|
||
{
|
||
int failedInCurrentRound = 0;
|
||
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"##################################################"
|
||
);
|
||
Console.WriteLine(
|
||
"ПРОХОД {0} ИЗ {1}",
|
||
round,
|
||
MaxRounds
|
||
);
|
||
Console.WriteLine(
|
||
"Ссылок в этом проходе: " +
|
||
pendingUrls.Count
|
||
);
|
||
Console.WriteLine(
|
||
"Уже завершено: " +
|
||
completedUrls.Count
|
||
);
|
||
Console.WriteLine(
|
||
"Всего ссылок: " +
|
||
allUrls.Count
|
||
);
|
||
Console.WriteLine(
|
||
"##################################################"
|
||
);
|
||
|
||
var nextRoundUrls = new List<string>();
|
||
|
||
for (
|
||
int index = 0;
|
||
index < pendingUrls.Count;
|
||
index++)
|
||
{
|
||
string url = pendingUrls[index];
|
||
|
||
int remainingBeforeDownload =
|
||
allUrls.Count - completedUrls.Count;
|
||
|
||
ShowProgress(
|
||
allUrls.Count,
|
||
completedUrls.Count,
|
||
remainingBeforeDownload,
|
||
failedInCurrentRound,
|
||
index + 1,
|
||
pendingUrls.Count,
|
||
round
|
||
);
|
||
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"=================================================="
|
||
);
|
||
Console.WriteLine(
|
||
"Ссылка {0} из {1}",
|
||
index + 1,
|
||
pendingUrls.Count
|
||
);
|
||
Console.WriteLine(url);
|
||
Console.WriteLine(
|
||
"=================================================="
|
||
);
|
||
|
||
bool success = DownloadWithRetries(
|
||
url,
|
||
index + 1,
|
||
pendingUrls.Count,
|
||
round
|
||
);
|
||
|
||
if (success)
|
||
{
|
||
completedUrls.Add(url);
|
||
SaveCompletedUrls(completedUrls);
|
||
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Ссылка успешно завершена."
|
||
);
|
||
}
|
||
else
|
||
{
|
||
failedInCurrentRound++;
|
||
nextRoundUrls.Add(url);
|
||
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Ссылка не завершена."
|
||
);
|
||
Console.WriteLine(
|
||
"Она будет повторена в следующем проходе."
|
||
);
|
||
}
|
||
|
||
int remainingAfterDownload =
|
||
allUrls.Count - completedUrls.Count;
|
||
|
||
ShowProgress(
|
||
allUrls.Count,
|
||
completedUrls.Count,
|
||
remainingAfterDownload,
|
||
failedInCurrentRound,
|
||
index + 1,
|
||
pendingUrls.Count,
|
||
round
|
||
);
|
||
|
||
// Сохраняем незавершённые ссылки после
|
||
// каждой обработки.
|
||
SaveFailedUrls(
|
||
nextRoundUrls.Concat(
|
||
pendingUrls.Skip(index + 1)
|
||
)
|
||
);
|
||
}
|
||
|
||
pendingUrls = nextRoundUrls
|
||
.Distinct(
|
||
StringComparer.OrdinalIgnoreCase
|
||
)
|
||
.ToList();
|
||
|
||
SaveFailedUrls(pendingUrls);
|
||
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"======================================="
|
||
);
|
||
Console.WriteLine(
|
||
" РЕЗУЛЬТАТ ПРОХОДА"
|
||
);
|
||
Console.WriteLine(
|
||
"======================================="
|
||
);
|
||
Console.WriteLine(
|
||
"Номер прохода: " + round
|
||
);
|
||
Console.WriteLine(
|
||
"Всего ссылок: " + allUrls.Count
|
||
);
|
||
Console.WriteLine(
|
||
"Успешно завершено: " +
|
||
completedUrls.Count
|
||
);
|
||
Console.WriteLine(
|
||
"Ошибок в проходе: " +
|
||
failedInCurrentRound
|
||
);
|
||
Console.WriteLine(
|
||
"Осталось скачать: " +
|
||
pendingUrls.Count
|
||
);
|
||
|
||
double roundPercent =
|
||
allUrls.Count == 0
|
||
? 0
|
||
: completedUrls.Count *
|
||
100.0 /
|
||
allUrls.Count;
|
||
|
||
Console.WriteLine(
|
||
"Общий прогресс: {0:0.0}%",
|
||
roundPercent
|
||
);
|
||
Console.WriteLine(
|
||
"======================================="
|
||
);
|
||
|
||
if (
|
||
pendingUrls.Count > 0 &&
|
||
round < MaxRounds)
|
||
{
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Через {0} секунд начнётся повтор " +
|
||
"незавершённых ссылок.",
|
||
RestartDelaySeconds
|
||
);
|
||
|
||
Thread.Sleep(
|
||
RestartDelaySeconds * 1000
|
||
);
|
||
}
|
||
|
||
round++;
|
||
}
|
||
|
||
ShowFinalResult(
|
||
allUrls.Count,
|
||
completedUrls.Count,
|
||
pendingUrls
|
||
);
|
||
|
||
WaitForExit();
|
||
}
|
||
|
||
private static bool DownloadWithRetries(
|
||
string url,
|
||
int urlNumber,
|
||
int totalUrlsInRound,
|
||
int round)
|
||
{
|
||
for (
|
||
int attempt = 1;
|
||
attempt <= MaxRunsPerUrl;
|
||
attempt++)
|
||
{
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Проход {0}/{1} | попытка {2}/{3} | " +
|
||
"ссылка {4}/{5}",
|
||
round,
|
||
MaxRounds,
|
||
attempt,
|
||
MaxRunsPerUrl,
|
||
urlNumber,
|
||
totalUrlsInRound
|
||
);
|
||
|
||
lock (OutputLock)
|
||
{
|
||
_lastOutputTime = DateTime.Now;
|
||
}
|
||
|
||
bool killedByWatchdog = false;
|
||
|
||
using (var process = new Process())
|
||
{
|
||
process.StartInfo.FileName = _ripPath;
|
||
|
||
process.StartInfo.Arguments =
|
||
"--no-progress --verbose url \"" +
|
||
url +
|
||
"\"";
|
||
|
||
process.StartInfo.UseShellExecute = false;
|
||
process.StartInfo.RedirectStandardOutput = true;
|
||
process.StartInfo.RedirectStandardError = true;
|
||
process.StartInfo.CreateNoWindow = false;
|
||
|
||
process.OutputDataReceived += OnOutput;
|
||
process.ErrorDataReceived += OnOutput;
|
||
|
||
try
|
||
{
|
||
process.Start();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine(
|
||
"Не удалось запустить rip.exe:"
|
||
);
|
||
Console.WriteLine(ex.Message);
|
||
return false;
|
||
}
|
||
|
||
process.BeginOutputReadLine();
|
||
process.BeginErrorReadLine();
|
||
|
||
while (!process.HasExited)
|
||
{
|
||
Thread.Sleep(1000);
|
||
|
||
double idleSeconds;
|
||
|
||
lock (OutputLock)
|
||
{
|
||
idleSeconds =
|
||
(
|
||
DateTime.Now -
|
||
_lastOutputTime
|
||
).TotalSeconds;
|
||
}
|
||
|
||
Console.Title = string.Format(
|
||
"Проход {0}/{1} | ссылка {2}/{3} | " +
|
||
"попытка {4}/{5} | без вывода {6:0} сек.",
|
||
round,
|
||
MaxRounds,
|
||
urlNumber,
|
||
totalUrlsInRound,
|
||
attempt,
|
||
MaxRunsPerUrl,
|
||
idleSeconds
|
||
);
|
||
|
||
if (
|
||
idleSeconds >=
|
||
IdleLimitSeconds)
|
||
{
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Нет вывода {0} секунд.",
|
||
IdleLimitSeconds
|
||
);
|
||
Console.WriteLine(
|
||
"Процесс будет перезапущен."
|
||
);
|
||
|
||
KillProcessTree(process.Id);
|
||
killedByWatchdog = true;
|
||
|
||
Thread.Sleep(
|
||
RestartDelaySeconds * 1000
|
||
);
|
||
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!killedByWatchdog)
|
||
{
|
||
try
|
||
{
|
||
process.WaitForExit();
|
||
|
||
if (process.ExitCode == 0)
|
||
{
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"rip.exe завершился успешно."
|
||
);
|
||
|
||
return true;
|
||
}
|
||
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"rip.exe завершился с кодом: " +
|
||
process.ExitCode
|
||
);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Ошибка при проверке процесса:"
|
||
);
|
||
Console.WriteLine(ex.Message);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (attempt < MaxRunsPerUrl)
|
||
{
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Повтор через {0} секунд...",
|
||
RestartDelaySeconds
|
||
);
|
||
|
||
Thread.Sleep(
|
||
RestartDelaySeconds * 1000
|
||
);
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private static void ShowProgress(
|
||
int totalUrls,
|
||
int completedCount,
|
||
int remainingCount,
|
||
int failedInCurrentRound,
|
||
int currentUrl,
|
||
int urlsInCurrentRound,
|
||
int currentRound)
|
||
{
|
||
double percent =
|
||
totalUrls == 0
|
||
? 0
|
||
: completedCount *
|
||
100.0 /
|
||
totalUrls;
|
||
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"**************** СТАТИСТИКА ****************"
|
||
);
|
||
Console.WriteLine(
|
||
"Всего ссылок: " +
|
||
totalUrls
|
||
);
|
||
Console.WriteLine(
|
||
"Успешно завершено: " +
|
||
completedCount
|
||
);
|
||
Console.WriteLine(
|
||
"Осталось скачать: " +
|
||
remainingCount
|
||
);
|
||
Console.WriteLine(
|
||
"Ошибок в текущем проходе: " +
|
||
failedInCurrentRound
|
||
);
|
||
Console.WriteLine(
|
||
"Текущая ссылка: {0} из {1}",
|
||
currentUrl,
|
||
urlsInCurrentRound
|
||
);
|
||
Console.WriteLine(
|
||
"Текущий проход: {0} из {1}",
|
||
currentRound,
|
||
MaxRounds
|
||
);
|
||
Console.WriteLine(
|
||
"Общий прогресс: {0:0.0}%",
|
||
percent
|
||
);
|
||
Console.WriteLine(
|
||
"********************************************"
|
||
);
|
||
|
||
Console.Title = string.Format(
|
||
"Готово {0}/{1} ({2:0.0}%) | осталось {3}",
|
||
completedCount,
|
||
totalUrls,
|
||
percent,
|
||
remainingCount
|
||
);
|
||
}
|
||
|
||
private static void ShowFinalResult(
|
||
int totalUrls,
|
||
int completedCount,
|
||
List<string> pendingUrls)
|
||
{
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"======================================="
|
||
);
|
||
Console.WriteLine(
|
||
" ИТОГОВЫЙ РЕЗУЛЬТАТ"
|
||
);
|
||
Console.WriteLine(
|
||
"======================================="
|
||
);
|
||
Console.WriteLine(
|
||
"Всего ссылок: " + totalUrls
|
||
);
|
||
Console.WriteLine(
|
||
"Успешно скачано: " + completedCount
|
||
);
|
||
Console.WriteLine(
|
||
"Осталось скачать: " + pendingUrls.Count
|
||
);
|
||
|
||
double finalPercent =
|
||
totalUrls == 0
|
||
? 0
|
||
: completedCount *
|
||
100.0 /
|
||
totalUrls;
|
||
|
||
Console.WriteLine(
|
||
"Выполнено: {0:0.0}%",
|
||
finalPercent
|
||
);
|
||
|
||
if (pendingUrls.Count == 0)
|
||
{
|
||
if (File.Exists(_failedPath))
|
||
{
|
||
File.Delete(_failedPath);
|
||
}
|
||
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Все ссылки успешно обработаны."
|
||
);
|
||
}
|
||
else
|
||
{
|
||
SaveFailedUrls(pendingUrls);
|
||
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Незавершённые ссылки:"
|
||
);
|
||
|
||
foreach (string failedUrl in pendingUrls)
|
||
{
|
||
Console.WriteLine("- " + failedUrl);
|
||
}
|
||
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Незавершённые ссылки сохранены в:"
|
||
);
|
||
Console.WriteLine(_failedPath);
|
||
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"При следующем запуске программа " +
|
||
"снова попробует их скачать."
|
||
);
|
||
}
|
||
|
||
Console.WriteLine(
|
||
"======================================="
|
||
);
|
||
|
||
Console.Title = string.Format(
|
||
"Завершено {0}/{1} | осталось {2}",
|
||
completedCount,
|
||
totalUrls,
|
||
pendingUrls.Count
|
||
);
|
||
}
|
||
|
||
private static string FindProjectRoot(
|
||
string startFolder)
|
||
{
|
||
DirectoryInfo current =
|
||
new DirectoryInfo(startFolder);
|
||
|
||
for (int i = 0; i < 8 && current != null; i++)
|
||
{
|
||
string downloads =
|
||
Path.Combine(current.FullName, "downloads");
|
||
|
||
string data =
|
||
Path.Combine(current.FullName, "data");
|
||
|
||
string tools =
|
||
Path.Combine(current.FullName, "tools");
|
||
|
||
if (
|
||
Directory.Exists(downloads) &&
|
||
Directory.Exists(data) &&
|
||
Directory.Exists(tools))
|
||
{
|
||
return current.FullName;
|
||
}
|
||
|
||
current = current.Parent;
|
||
}
|
||
|
||
return startFolder;
|
||
}
|
||
|
||
private static string FindRipExecutable(
|
||
string projectRoot)
|
||
{
|
||
string localRip = Path.Combine(
|
||
projectRoot,
|
||
"tools",
|
||
"rip.exe"
|
||
);
|
||
|
||
if (File.Exists(localRip))
|
||
{
|
||
return localRip;
|
||
}
|
||
|
||
string pathValue =
|
||
Environment.GetEnvironmentVariable("PATH");
|
||
|
||
if (string.IsNullOrWhiteSpace(pathValue))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
foreach (
|
||
string folder in pathValue.Split(
|
||
new[] { ';' },
|
||
StringSplitOptions.RemoveEmptyEntries))
|
||
{
|
||
try
|
||
{
|
||
string candidate = Path.Combine(
|
||
folder.Trim(),
|
||
"rip.exe"
|
||
);
|
||
|
||
if (File.Exists(candidate))
|
||
{
|
||
return candidate;
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private static bool ConfigureStreamripDownloadFolder(
|
||
string downloadsFolder)
|
||
{
|
||
string appData =
|
||
Environment.GetFolderPath(
|
||
Environment.SpecialFolder.ApplicationData
|
||
);
|
||
|
||
string configPath = Path.Combine(
|
||
appData,
|
||
"streamrip",
|
||
"config.toml"
|
||
);
|
||
|
||
if (!File.Exists(configPath))
|
||
{
|
||
Console.WriteLine(
|
||
"Не найден конфигурационный файл Streamrip:"
|
||
);
|
||
Console.WriteLine(configPath);
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Сначала запустите команду rip хотя бы один раз " +
|
||
"и настройте доступ к Qobuz."
|
||
);
|
||
return false;
|
||
}
|
||
|
||
try
|
||
{
|
||
string[] lines = File.ReadAllLines(
|
||
configPath,
|
||
Encoding.UTF8
|
||
);
|
||
|
||
string escapedPath =
|
||
downloadsFolder.Replace("\\", "\\\\");
|
||
|
||
bool inDownloadsSection = false;
|
||
bool folderUpdated = false;
|
||
var result = new List<string>();
|
||
|
||
foreach (string originalLine in lines)
|
||
{
|
||
string trimmed = originalLine.Trim();
|
||
|
||
if (
|
||
trimmed.StartsWith("[") &&
|
||
trimmed.EndsWith("]"))
|
||
{
|
||
if (
|
||
inDownloadsSection &&
|
||
!folderUpdated)
|
||
{
|
||
result.Add(
|
||
"folder = \"" +
|
||
escapedPath +
|
||
"\""
|
||
);
|
||
folderUpdated = true;
|
||
}
|
||
|
||
inDownloadsSection =
|
||
string.Equals(
|
||
trimmed,
|
||
"[downloads]",
|
||
StringComparison.OrdinalIgnoreCase
|
||
);
|
||
}
|
||
|
||
if (
|
||
inDownloadsSection &&
|
||
trimmed.StartsWith(
|
||
"folder",
|
||
StringComparison.OrdinalIgnoreCase
|
||
) &&
|
||
trimmed.Contains("="))
|
||
{
|
||
result.Add(
|
||
"folder = \"" +
|
||
escapedPath +
|
||
"\""
|
||
);
|
||
folderUpdated = true;
|
||
}
|
||
else
|
||
{
|
||
result.Add(originalLine);
|
||
}
|
||
}
|
||
|
||
if (inDownloadsSection && !folderUpdated)
|
||
{
|
||
result.Add(
|
||
"folder = \"" +
|
||
escapedPath +
|
||
"\""
|
||
);
|
||
folderUpdated = true;
|
||
}
|
||
|
||
if (!folderUpdated)
|
||
{
|
||
Console.WriteLine(
|
||
"В config.toml не найден раздел [downloads]."
|
||
);
|
||
Console.WriteLine(
|
||
"Проверьте конфигурацию Streamrip."
|
||
);
|
||
return false;
|
||
}
|
||
|
||
File.WriteAllLines(
|
||
configPath,
|
||
result,
|
||
new UTF8Encoding(false)
|
||
);
|
||
|
||
Console.WriteLine(
|
||
"Папка загрузок Streamrip настроена:"
|
||
);
|
||
Console.WriteLine(downloadsFolder);
|
||
Console.WriteLine();
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine(
|
||
"Не удалось обновить config.toml Streamrip:"
|
||
);
|
||
Console.WriteLine(ex.Message);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
private static List<string> ReadUrls(
|
||
string path)
|
||
{
|
||
return File.ReadAllLines(
|
||
path,
|
||
Encoding.UTF8
|
||
)
|
||
.Select(line => line.Trim())
|
||
.Where(
|
||
line =>
|
||
!string.IsNullOrWhiteSpace(line) &&
|
||
!line.StartsWith("#")
|
||
)
|
||
.Distinct(
|
||
StringComparer.OrdinalIgnoreCase
|
||
)
|
||
.ToList();
|
||
}
|
||
|
||
private static HashSet<string> ReadUrlSet(
|
||
string path)
|
||
{
|
||
if (!File.Exists(path))
|
||
{
|
||
return new HashSet<string>(
|
||
StringComparer.OrdinalIgnoreCase
|
||
);
|
||
}
|
||
|
||
return new HashSet<string>(
|
||
File.ReadAllLines(
|
||
path,
|
||
Encoding.UTF8
|
||
)
|
||
.Select(line => line.Trim())
|
||
.Where(
|
||
line =>
|
||
!string.IsNullOrWhiteSpace(line)
|
||
),
|
||
StringComparer.OrdinalIgnoreCase
|
||
);
|
||
}
|
||
|
||
private static void SaveCompletedUrls(
|
||
IEnumerable<string> completedUrls)
|
||
{
|
||
try
|
||
{
|
||
File.WriteAllLines(
|
||
_completedPath,
|
||
completedUrls
|
||
.Distinct(
|
||
StringComparer.OrdinalIgnoreCase
|
||
)
|
||
.OrderBy(url => url),
|
||
Encoding.UTF8
|
||
);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Не удалось сохранить completed_urls.txt:"
|
||
);
|
||
Console.WriteLine(ex.Message);
|
||
}
|
||
}
|
||
|
||
private static void SaveFailedUrls(
|
||
IEnumerable<string> failedUrls)
|
||
{
|
||
try
|
||
{
|
||
List<string> urls = failedUrls
|
||
.Where(
|
||
url =>
|
||
!string.IsNullOrWhiteSpace(url)
|
||
)
|
||
.Distinct(
|
||
StringComparer.OrdinalIgnoreCase
|
||
)
|
||
.ToList();
|
||
|
||
if (urls.Count == 0)
|
||
{
|
||
if (File.Exists(_failedPath))
|
||
{
|
||
File.Delete(_failedPath);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
File.WriteAllLines(
|
||
_failedPath,
|
||
urls,
|
||
Encoding.UTF8
|
||
);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Не удалось сохранить failed_urls.txt:"
|
||
);
|
||
Console.WriteLine(ex.Message);
|
||
}
|
||
}
|
||
|
||
private static void OnOutput(
|
||
object sender,
|
||
DataReceivedEventArgs e)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(e.Data))
|
||
{
|
||
return;
|
||
}
|
||
|
||
lock (OutputLock)
|
||
{
|
||
_lastOutputTime = DateTime.Now;
|
||
}
|
||
|
||
Console.WriteLine(e.Data);
|
||
}
|
||
|
||
private static void KillProcessTree(
|
||
int pid)
|
||
{
|
||
try
|
||
{
|
||
var startInfo =
|
||
new ProcessStartInfo
|
||
{
|
||
FileName = "taskkill",
|
||
Arguments =
|
||
"/PID " + pid + " /T /F",
|
||
CreateNoWindow = true,
|
||
UseShellExecute = false,
|
||
RedirectStandardOutput = true,
|
||
RedirectStandardError = true
|
||
};
|
||
|
||
using (
|
||
Process taskkill =
|
||
Process.Start(startInfo))
|
||
{
|
||
if (taskkill != null)
|
||
{
|
||
taskkill.WaitForExit();
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Не удалось завершить процесс:"
|
||
);
|
||
Console.WriteLine(ex.Message);
|
||
}
|
||
}
|
||
|
||
private static void WaitForExit()
|
||
{
|
||
Console.WriteLine();
|
||
Console.WriteLine(
|
||
"Нажми Enter для выхода."
|
||
);
|
||
Console.ReadLine();
|
||
}
|
||
}
|
||
} |