using System; using System.IO; using System.IO.Compression; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json.Linq; using Pf2e.Scrapper.Helpers; namespace Pf2e.Scrapper.Importers.Repositories; public abstract class AbstractGitHubRepository(AbstractImporter importer) : AbstractImporterRepository(importer) { public abstract string Repository { get; } public abstract string AssetName { get; } public override void Update(ProcessProgress progress) { ReleaseInformation releaseInformation = GetReleaseInformation(); string? currentVersion = GetCurrentVersion(); if (currentVersion == releaseInformation.TagName) { progress.Local.Complete(); progress.Information( $"Данные {Repository} актуальны, текущая версия {currentVersion}." ); return; } progress.Information( $"Данные {Repository} устарели, обновляем {RepositoryDirPath} до версии {releaseInformation.TagName}..." ); string assetFile = DownloadAsset(progress, releaseInformation); ExtractAsset(assetFile, RepositoryDirPath, progress); File.WriteAllText( Path.Combine(RepositoryDirPath, "version.txt"), releaseInformation.TagName ); } private ReleaseInformation GetReleaseInformation() { string releasesUrl = $"https://api.github.com/repos/{Repository}/releases/latest"; using HttpClient client = new(); client.DefaultRequestHeaders.UserAgent.Add( new ProductInfoHeaderValue("Pf2eDataScrapper", "1.0") ); string json = client.GetStringAsync(releasesUrl).Result; JObject jObject = JObject.Parse(json); return new() { TagName = jObject.SelectToken("tag_name")!.ToObject()!, DownloadUrl = jObject .SelectToken("assets[?(@.name == '" + AssetName + "')].browser_download_url")! .ToObject()!, Size = jObject .SelectToken("assets[?(@.name == '" + AssetName + "')].size")! .ToObject()!, }; } private string? GetCurrentVersion() { string versionFilePath = Path.Combine(RepositoryDirPath, "version.txt"); if (File.Exists(versionFilePath)) { return File.ReadAllText(versionFilePath).Trim(); } return null; } private string DownloadAsset(ProcessProgress progress, ReleaseInformation releaseInformation) { string fileName = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".zip"; using var fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write); progress.Local.Reset(releaseInformation.Size); progress.Local.Start( $"[1/2] Загрузка {releaseInformation.TagName} ({releaseInformation.Size / 1024 / 1024} МБ)..." ); Task.Run(async () => { using HttpClient client = new(); using var response = await client.GetAsync( releaseInformation.DownloadUrl, HttpCompletionOption.ResponseHeadersRead ); using var download = await response.Content.ReadAsStreamAsync(); var buffer = new byte[81920]; int bytesRead; var t = CancellationToken.None; while ((bytesRead = await download.ReadAsync(buffer, t).ConfigureAwait(false)) != 0) { await fileStream .WriteAsync(buffer.AsMemory(0, bytesRead), t) .ConfigureAwait(false); progress.Local.Current += bytesRead; } }) .Wait(); progress.Local.Complete(); return fileName; } private void ExtractAsset(string assetFile, string targetDir, ProcessProgress progress) { using var zip = ZipFile.OpenRead(assetFile); progress.Local.Reset(zip.Entries.Count); progress.Local.Start("[2/2] Распаковка архива..."); foreach (var entry in zip.Entries) { if (string.IsNullOrEmpty(entry.Name)) continue; // Skip directories string destinationPath = Path.Combine(targetDir, entry.FullName); string directoryPath = Path.GetDirectoryName(destinationPath)!; if (!Directory.Exists(directoryPath)) { Directory.CreateDirectory(directoryPath); } entry.ExtractToFile(destinationPath, true); progress.Local.Current++; } progress.Local.Complete(); } private class ReleaseInformation { public string TagName { get; init; } = null!; public string DownloadUrl { get; init; } = null!; public int Size { get; init; } = 0; } }