-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathProgram.cs
298 lines (248 loc) · 7.78 KB
/
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Downloader;
using Downloader.Models;
using Microsoft.Extensions.Configuration;
namespace ItvdnCoursesDownloaderConsole;
internal class Program
{
private static Engine _engine;
private static readonly object _consoleWriterLock = new();
private static async Task Main(string[] args)
{
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.AddUserSecrets<Program>(true, true)
.AddCommandLine(args)
.Build();
AppSettings appSettings = await GetSettingsAsync(configuration);
if (appSettings is null)
{
return;
}
using CancellationTokenSource cancellation = new();
_engine = new Engine(appSettings.Engine, cancellation.Token);
string courseAddress = appSettings.CourseAddress;
do
{
if (courseAddress is null)
{
Console.Clear();
Console.Write("Адрес курса: ");
courseAddress = Console.ReadLine();
}
Console.Clear();
ShowInfo(appSettings.Engine);
if (Uri.TryCreate(courseAddress, UriKind.Absolute, out Uri courseUri))
{
Console.WriteLine("Загрузка информации о курсе...");
try
{
bool result = await DownloadCourseAsync(courseUri);
}
catch (Exception exception)
{
await ShowErrorMessageAsync($"Ошибка: {GetErrorDescription(exception)}");
}
}
else
{
await ShowErrorMessageAsync($"Некорректный адрес курса: {courseAddress}");
}
courseAddress = null;
} while (OneMoreCourseRequest());
}
/// <summary>
/// Получает настройки приложения из всех предусмотренных источников.
/// </summary>
/// <param name="configuration">Набор свойств конфигурации приложения.</param>
/// <returns>Настройки приложения.</returns>
private static async Task<AppSettings> GetSettingsAsync(IConfiguration configuration)
{
AppSettings appSettings = configuration.Get<AppSettings>();
if (appSettings is null)
{
await ShowErrorMessageAsync("Не найдены параметры приложения.");
return null;
}
List<ValidationResult> validationResults = new();
Validator.TryValidateObject(
appSettings,
new ValidationContext(appSettings),
validationResults,
true);
Validator.TryValidateObject(
appSettings.Engine,
new ValidationContext(appSettings.Engine),
validationResults,
true);
if (validationResults.Count > 0)
{
await ShowErrorMessageAsync("Ошибки в параметрах приложения:");
StringBuilder errorMessageBuilder = new();
foreach (ValidationResult error in validationResults)
{
errorMessageBuilder.Clear();
errorMessageBuilder.Append(error.ErrorMessage);
if (error.MemberNames.Any())
{
errorMessageBuilder
.Append("\r\n\t")
.Append("Параметры: ")
.AppendJoin(',', error.MemberNames);
}
await ShowErrorMessageAsync(errorMessageBuilder.ToString());
}
return null;
}
return appSettings;
}
/// <summary>
/// Выводит общую информацию сеанса загрузки данных.
/// </summary>
/// <param name="engineSettings">Настройки движка загрузки данных.</param>
private static void ShowInfo(EngineSettings engineSettings)
{
Console.WriteLine("Адрес электронной почты: " + engineSettings.Email);
Console.WriteLine("Путь сохранения файлов: " + engineSettings.SavePath);
Console.WriteLine(new string('-', Console.WindowWidth));
}
/// <summary>
/// Выводит в стандартный выходной поток сообщений об ошибках очередное сообщение.
/// </summary>
/// <param name="errorMessage">Текст сообщения об ошибке.</param>
private static async Task ShowErrorMessageAsync(string errorMessage)
{
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.Red;
await Console.Error.WriteLineAsync(errorMessage);
Console.ResetColor();
}
private static bool OneMoreCourseRequest()
{
Console.WriteLine("Загрузить ещё один курс?");
Console.WriteLine("1. Да");
Console.WriteLine("2. Нет");
while (true)
{
switch (Console.ReadKey(true).KeyChar)
{
case '1':
return true;
case '2':
return false;
default:
Console.Write("\a\b \b");
break;
}
}
}
private static async Task<bool> DownloadCourseAsync(Uri uri)
{
Course course = null;
try
{
course = await _engine.GetCourseAsync(uri);
}
catch (Exception exception)
{
await ShowErrorMessageAsync(exception.Message);
}
if (course == null)
{
Console.WriteLine("Данные не получены.");
return false;
}
if (string.IsNullOrWhiteSpace(course.Title))
{
Console.WriteLine("Не удалось извлечь данные курса.");
}
else
{
Console.Clear();
Console.BackgroundColor = ConsoleColor.Blue;
Console.WriteLine(course.Title);
Console.ResetColor();
if (course.IncorrectFiles.Count > 0)
{
Console.WriteLine("Не удалось получить ссылки для следующих файлов:");
foreach (string fileTitle in course.IncorrectFiles)
{
Console.WriteLine($" - {fileTitle}");
}
}
else
{
Console.WriteLine("Файлы курса:");
Console.CursorVisible = false;
await DownloadFilesAsync(course);
//await ShowErrorMessageAsync("Заглушка - загрузка файлов"); // TODO: Убрать заглушку.
}
}
return true;
}
private static async Task DownloadFilesAsync(Course course)
{
List<DownloadFile> downloadFiles = course.CorrectFiles;
foreach (DownloadFile file in downloadFiles)
{
int cursorTop = Console.CursorTop;
WriteFileInfo(file, cursorTop);
file.SizeChanged += (sender, e) => WriteFileInfo(file, cursorTop);
file.ProgressPercentageChanged += (sender, e) => WriteFileInfo(file, cursorTop);
file.StatusChanged += (sender, e) => WriteFileInfo(file, cursorTop);
}
int footerCursorTop = Console.CursorTop;
bool result = await _engine.DownloadFilesAsync(course);
Console.CursorVisible = true;
Console.CursorTop = footerCursorTop;
if (result)
{
Console.WriteLine("Все файлы загружены.");
}
else
{
Console.WriteLine("Загружены не все файлы.");
foreach (DownloadFile file in downloadFiles.Where(file => file.Status == DownloadFileStatus.Error))
{
Console.Write($"{file.Title}: ");
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(GetErrorDescription(file.Error));
Console.ResetColor();
}
}
}
private static void WriteFileInfo(DownloadFile file, int cursorTop)
{
string fileInfo = $"{file.ProgressPercentage,4:D}% из {file.FormattedSize}: {file.Title}";
ConsoleColor color = file.Status switch
{
DownloadFileStatus.InProgress => ConsoleColor.Yellow,
DownloadFileStatus.Error => ConsoleColor.Red,
DownloadFileStatus.Completed => ConsoleColor.Green,
_ => Console.ForegroundColor,
};
lock (_consoleWriterLock)
{
Console.CursorTop = cursorTop;
Console.ForegroundColor = color;
Console.WriteLine(fileInfo);
Console.ResetColor();
}
}
private static string GetErrorDescription(Exception exception)
{
Exception currentException = exception;
List<string> errorMessages = new();
do
{
errorMessages.Add(currentException.Message);
} while ((currentException = currentException.InnerException) != null);
return string.Join(" -> ", errorMessages);
}
}