/// <summary> /// Создаёт и возвращает новое задание по скачиванию указанных песен и последующему сохранению их на диск по указанному пути /// </summary> /// <param name="Songs">Список всех хидеров песен, файлы песен для которых следует скачать и вернуть. /// Не может быть NULL или пустым.</param> /// <param name="UserAgent">User-Agent, который будет использоваться при выполнении запросов</param> /// <param name="FolderPath">Существующий путь на диске, по которому будут сохранены песни. /// Если NULL, некорректный или не существует, будет выброшено исключение.</param> /// <param name="GenerateNewFilenames">Определяет, следует ли генерировать новое имя файла на основании тэгов песни (true), /// или же использовать то имя файла, которое "пришло" с сервера (false). Если будет указана генерация нового, однако /// получившееся имя будет некорректным, метод попытается его исправить. /// Если же исправить не получится, будет использовано имя с сервера.</param> /// <param name="FilenameTemplate">Шаблон имени файла песни</param> /// <returns></returns> public static ReactiveDownloader CreateTask (IList<OneSongHeader> Songs, String UserAgent, String FolderPath, Boolean GenerateNewFilenames, String FilenameTemplate) { if (UserAgent.HasAlphaNumericChars() == false) { throw new ArgumentException("User-Agent не может быть пустым", "UserAgent"); } if (FilePathTools.IsValidFilePath(FolderPath) == false) { throw new ArgumentException ("Путь для сохранения файлов песен = '" + FolderPath.ToStringS("NULL") + "' некорректен", "FolderPath"); } if(Songs == null) {throw new ArgumentNullException("Songs");} if(Songs.Any()==false) {throw new ArgumentException("Список песен для обработки не может быть пустым", "Songs");} if(GenerateNewFilenames == true && FilenameTemplate.HasAlphaNumericChars()==false) {throw new ArgumentException("Шаблон имени файла не может быть пуст, если требуется клиентская генерация имён файлов");} return new ReactiveDownloader(Songs, UserAgent, FolderPath, GenerateNewFilenames, FilenameTemplate); }
/// <summary> /// Пытается очистить от шелухи и возвратить URI /// </summary> /// <param name="Input"></param> /// <param name="ErrorMessage"></param> /// <returns></returns> internal static Uri TryFixReturnURI(String Input, out String ErrorMessage) { ErrorMessage = null; if (Input.HasAlphaNumericChars() == false) { ErrorMessage = "URI = '" + Input.ToStringS("NULL") + "' заведомо некорректен"; return null; } const String main_domain = "http://myzuka.ru"; const String download_domain = "http://fp"; String temp_URI = Input.Trim(new char[2] { ' ', '"' }); if (temp_URI.StartsWith(main_domain, StringComparison.OrdinalIgnoreCase) == false && temp_URI.StartsWith(download_domain, StringComparison.OrdinalIgnoreCase) == false) { temp_URI = main_domain + temp_URI; } temp_URI = HttpTools.DecodeUri(temp_URI); Uri link_URI; Boolean res = Uri.TryCreate(temp_URI, UriKind.Absolute, out link_URI); if (res == false) { ErrorMessage = String.Format( "Невозможно корректно распарсить как абсолютный URI подстроку '{0}', полученную из строки '{1}'", temp_URI, Input); return null; } return link_URI; }
/// <summary> /// Асинхронно, без задержки вызывающего потока, скачивает и сохраняет на диск по указанному пути все песни, указанные в наборе, /// выполняя запросы к серверу с указанной степенью паралеллизма и с указанным токеном отмены операции /// </summary> /// <param name="Songs">Список всех хидеров песен, файлы песен для которых следует скачать и вернуть</param> /// <param name="UserAgent">UserAgent, который будет использоваться при выполнении запросов</param> /// <param name="FolderPath">Существующий путь на диске, по которому будут сохранены песни. /// Если NULL, некорректный или не существует, будет выброшено исключение.</param> /// <param name="GenerateNewFilenames">Определяет, следует ли генерировать новое имя файла на основании тэгов песни (true), /// или же использовать то имя файла, которое "пришло" с сервера (false). Если будет указана генерация нового, однако /// получившееся имя будет некорректным, метод попытается его исправить. /// Если же исправить не получится, будет использовано имя с сервера.</param> /// <param name="FilenameTemplate"></param> /// <param name="CancToken">Токен отмены операции</param> /// <param name="MaxDegreeOfParallelism">Максимальное количество потоков, которое будет использоваться для запросов к серверу. /// Если меньше 1, ограничение на количество потоков будет снято.</param> /// <returns></returns> public static async Task<IDictionary<OneSongHeader, Exception>> TryDownloadAndSaveAllSongsAsync (IList<OneSongHeader> Songs, String UserAgent, String FolderPath, Boolean GenerateNewFilenames, String FilenameTemplate, CancellationToken CancToken, Int32 MaxDegreeOfParallelism) { Songs.ThrowIfNullOrEmpty(); if (UserAgent == null) { throw new ArgumentNullException("UserAgent"); } if (FilePathTools.IsValidFilePath(FolderPath) == false) { throw new ArgumentException("Путь = '" + FolderPath.ToStringS("NULL") + "' некорректен", "FolderPath"); } if (MaxDegreeOfParallelism < 1) { MaxDegreeOfParallelism = -1; } return await Task.Factory.StartNew<IDictionary<OneSongHeader, Exception>>( () => Core.TryDownloadAndSaveAllSongs(Songs, UserAgent, FolderPath, GenerateNewFilenames, FilenameTemplate, CancToken, MaxDegreeOfParallelism), CancToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); }
/// <summary> /// Скачивает и сохраняет на диск по указанному пути все песни, указанные в наборе, /// выполняя запросы к серверу с указанной степенью паралеллизма и с указанным токеном отмены операции /// </summary> /// <param name="Songs">Список всех хидеров песен, файлы песен для которых следует скачать и вернуть</param> /// <param name="UserAgent">UserAgent, который будет использоваться при выполнении запросов</param> /// <param name="FolderPath">Существующий путь на диске, по которому будут сохранены песни. /// Если NULL, некорректный или не существует, будет выброшено исключение.</param> /// <param name="GenerateNewFilenames">Определяет, следует ли генерировать новое имя файла на основании тэгов песни (true), /// или же использовать то имя файла, которое "пришло" с сервера (false). Если будет указана генерация нового, однако /// получившееся имя будет некорректным, метод попытается его исправить. /// Если же исправить не получится, будет использовано имя с сервера.</param> /// <param name="FilenameTemplate"></param> /// <param name="CancToken">Токен отмены операции</param> /// <param name="MaxDegreeOfParallelism">Максимальное количество потоков, которое будет использоваться для запросов к серверу. /// Если меньше 1, ограничение на количество потоков будет снято.</param> /// <returns>Словарь ключей и значений, где ключ - это поданный на вход хидер песни, а значение - возможное исключение, /// которое возникло в процессе скачивания и сохранения песни, или же NULL, если песня была успешно скачана и сохранена.</returns> public static IDictionary<OneSongHeader, Exception> TryDownloadAndSaveAllSongs (IList<OneSongHeader> Songs, String UserAgent, String FolderPath, Boolean GenerateNewFilenames, String FilenameTemplate, CancellationToken CancToken, Int32 MaxDegreeOfParallelism) { Songs.ThrowIfNullOrEmpty(); if (UserAgent == null) { throw new ArgumentNullException("UserAgent"); } if (FilePathTools.IsValidFilePath(FolderPath) == false) {throw new ArgumentException("Путь = '" + FolderPath.ToStringS("NULL") + "' некорректен", "FolderPath");} if (MaxDegreeOfParallelism < 1) {MaxDegreeOfParallelism = -1;} ConcurrentDictionary<OneSongHeader, Exception> intermediate = new ConcurrentDictionary<OneSongHeader, Exception>(MaxDegreeOfParallelism, Songs.Count); ParallelOptions opt = new ParallelOptions() { CancellationToken = CancToken, MaxDegreeOfParallelism = MaxDegreeOfParallelism }; ParallelLoopResult p_res = Parallel.ForEach(Songs, opt, (OneSongHeader song, ParallelLoopState pls, Int64 i) => { opt.CancellationToken.ThrowIfCancellationRequested(); if(pls.ShouldExitCurrentIteration){pls.Break();} KeyValuePair<OneSongHeader, Exception> res = Core.DownloadAndSaveOneSong(song, UserAgent, GenerateNewFilenames, FilenameTemplate, FolderPath, (Int32)i + 1); intermediate.TryAdd(res.Key, res.Value); } ); return intermediate; }