Add project files
This commit is contained in:
61
.gitignore
vendored
Normal file
61
.gitignore
vendored
Normal 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
194
Program.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
16
Properties/PublishProfiles/FolderProfile.pubxml
Normal file
16
Properties/PublishProfiles/FolderProfile.pubxml
Normal 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
17
WSiPBookDownloader.csproj
Normal 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
3
WSiPBookDownloader.slnx
Normal file
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="WSiPBookDownloader.csproj" />
|
||||
</Solution>
|
||||
Reference in New Issue
Block a user