Add project files

This commit is contained in:
2026-06-17 22:09:16 +02:00
parent a7e1aa2127
commit d78e7612d3
5 changed files with 291 additions and 0 deletions

61
.gitignore vendored Normal file
View File

@@ -0,0 +1,61 @@
## A streamlined .gitignore for modern .NET projects
## including temporary files, build results, and
## files generated by popular .NET tools. If you are
## developing with Visual Studio, the VS .gitignore
## https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
## has more thorough IDE-specific entries.
##
## Get latest from https://github.com/github/gitignore/blob/main/Dotnet.gitignore
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# dotenv environment variables file
.env
# Others
~$*
*~
CodeCoverage/
# MSBuild Binary and Structured Log
*.binlog
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Custom
Properties/launchsettings.json
*.user

194
Program.cs Normal file
View File

@@ -0,0 +1,194 @@
using System.Diagnostics;
using System.Net;
using PdfSharp.Drawing;
using PdfSharp.Pdf;
namespace WSiPBookDownloader;
internal class BookMissingException : Exception
{
public int BookId { get; }
public BookMissingException(int bookId)
: base($"Book with ID {bookId} does not exist.")
{
BookId = bookId;
}
}
internal class Program
{
private static readonly HttpClient httpClient = new
(
new HttpClientHandler
{
AllowAutoRedirect = false,
}
)
{
BaseAddress = new Uri("https://appwsipnet.eduranga.pl/"),
};
private static readonly Lock _consoleLock = new();
private static void Log(string message, ConsoleColor? color = null)
{
lock (_consoleLock)
{
if (color.HasValue)
Console.ForegroundColor = color.Value;
Console.WriteLine(message);
Console.ResetColor();
}
}
static async Task Main(string[] args)
{
if (args.Length < 2 || args.Length > 4)
{
Log("Usage: [outputDirectory] [startBookId] [endBookId] [--only-covers]", ConsoleColor.DarkRed);
return;
}
var outputDirectory = args[0];
if (!Path.Exists(outputDirectory))
{
Log($"Output directory \"{outputDirectory}\" does not exist.", ConsoleColor.DarkRed);
return;
}
var startBookId = int.Parse(args[1]);
var endBookId = (args.Length >= 3 && int.TryParse(args[2], out var parsedEndBookId))
? parsedEndBookId
: startBookId;
var onlyCovers = args.FirstOrDefault(arg => arg.Equals("--only-covers", StringComparison.OrdinalIgnoreCase)) != null;
var bookIds = Enumerable.Range(startBookId, endBookId - startBookId + 1);
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = onlyCovers ? 100 : 10 };
await Parallel.ForEachAsync(bookIds, parallelOptions, async (bookId, cancellationToken) =>
{
var outputPath = Path.Combine(outputDirectory, $"{bookId}.pdf");
if (File.Exists(outputPath))
{
Log($"[!] Book with ID {bookId} already downloaded, skipping...", ConsoleColor.Yellow);
return;
}
Log($"[?] Starting download of book with ID {bookId}", ConsoleColor.Cyan);
var jpgDir = Path.Combine(Path.GetTempPath(), "WSiPBookDownloader", bookId.ToString());
try
{
if (onlyCovers)
{
await DownloadBookCoverJpg(bookId, outputDirectory, cancellationToken);
Log($"[✓] Downloaded book cover {bookId}", ConsoleColor.DarkGreen);
return;
}
await DownloadBookJpg(bookId, jpgDir, cancellationToken);
BuildPdf(bookId, jpgDir, outputPath);
Log($"[✓] Downloaded book {bookId}", ConsoleColor.DarkGreen);
}
catch (Exception ex)
{
Log($"[×] Failed to download book {bookId}:\n {ex.Message}", ConsoleColor.DarkRed);
} finally
{
if (Directory.Exists(jpgDir))
Directory.Delete(jpgDir, recursive: true);
}
});
}
private static async Task DownloadBookCoverJpg(int bookId, string outputDir, CancellationToken cancellationToken = default)
{
var outputPath = Path.Combine(outputDir, $"{bookId}.jpg");
if (File.Exists(outputPath))
{
Debug.WriteLine($"Cover for book {bookId} already exists, skipping...");
return;
}
var response = await httpClient.GetAsync($"e-podreczniki/podglad/{bookId}/files/mobile/1.jpg", cancellationToken);
if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Found)
throw new BookMissingException(bookId);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsByteArrayAsync(cancellationToken);
Directory.CreateDirectory(outputDir);
await File.WriteAllBytesAsync(outputPath, content, cancellationToken);
Debug.WriteLine($"Downloaded cover for book {bookId}");
}
private static async Task DownloadBookJpg(int bookId, string outputDir, CancellationToken cancellationToken = default)
{
var currentPage = 1;
while (true)
{
var outputPath = Path.Combine(outputDir, $"{currentPage}.jpg");
if (File.Exists(outputPath))
{
Debug.WriteLine($"Page {currentPage} of book {bookId} already exists, skipping...");
currentPage++;
continue;
}
var response = await httpClient.GetAsync($"e-podreczniki/podglad/{bookId}/files/mobile/{currentPage}.jpg", cancellationToken);
if (
(response.StatusCode == HttpStatusCode.Found || response.StatusCode == HttpStatusCode.NotFound) &&
currentPage > 1
)
{
Debug.WriteLine($"No more pages for book {bookId}, stopping...");
break;
} else if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Found)
throw new BookMissingException(bookId);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsByteArrayAsync(cancellationToken);
Directory.CreateDirectory(outputDir);
await File.WriteAllBytesAsync(outputPath, content, cancellationToken);
Debug.WriteLine($"Downloaded page {currentPage} of book {bookId}");
currentPage++;
}
}
private static void BuildPdf(int bookId, string jpgDir, string outputPath)
{
var pages = Directory.GetFiles(jpgDir, "*.jpg")
.OrderBy(file => int.Parse(Path.GetFileNameWithoutExtension(file)))
.ToList();
using var document = new PdfDocument();
document.Options.FlateEncodeMode = PdfFlateEncodeMode.BestCompression;
document.Options.UseFlateDecoderForJpegImages = PdfUseFlateDecoderForJpegImages.Never;
foreach (var pagePath in pages)
{
using var stream = new MemoryStream(File.ReadAllBytes(pagePath));
using var image = XImage.FromStream(stream);
var pdfPage = document.AddPage();
pdfPage.Width = XUnit.FromPoint(image.PointWidth);
pdfPage.Height = XUnit.FromPoint(image.PointHeight);
using var gfx = XGraphics.FromPdfPage(pdfPage);
gfx.DrawImage(image, 0, 0, image.PointWidth, image.PointHeight);
Debug.WriteLine($"Added page {Path.GetFileNameWithoutExtension(pagePath)} to PDF for book {bookId}");
}
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!);
document.Save(outputPath);
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\net10.0\win-x64\publish\win-x64\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishReadyToRun>false</PublishReadyToRun>
<PublishTrimmed>false</PublishTrimmed>
</PropertyGroup>
</Project>

17
WSiPBookDownloader.csproj Normal file
View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PDFsharp" Version="6.2.4" />
</ItemGroup>
</Project>

3
WSiPBookDownloader.slnx Normal file
View File

@@ -0,0 +1,3 @@
<Solution>
<Project Path="WSiPBookDownloader.csproj" />
</Solution>