Add more Environment Variables

This commit is contained in:
2025-09-18 01:46:53 +02:00
parent 33cb8a2ab3
commit d9a1923a3c
10 changed files with 67 additions and 58 deletions

View File

@@ -37,7 +37,7 @@ public class MangaConnectorController(MangaContext context) : Controller
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
public Results<Ok<MangaConnector>, NotFound<string>> GetConnector(string MangaConnectorName)
{
if(Tranga.MangaConnectors.FirstOrDefault(c => c.Name.Equals(MangaConnectorName, StringComparison.InvariantCultureIgnoreCase)) is not { } connector)
if(!Tranga.TryGetMangaConnector(MangaConnectorName, out MangaConnectors.MangaConnector? connector))
return TypedResults.NotFound(nameof(MangaConnectorName));
return TypedResults.Ok(new MangaConnector(connector.Name, connector.Enabled, connector.IconUrl, connector.SupportedLanguages));
@@ -65,7 +65,6 @@ public class MangaConnectorController(MangaContext context) : Controller
[ProducesResponseType<List<MangaConnector>>(Status200OK, "application/json")]
public Ok<List<MangaConnector>> GetDisabledConnectors()
{
return TypedResults.Ok(Tranga.MangaConnectors
.Where(c => c.Enabled == false)
.Select(c => new MangaConnector(c.Name, c.Enabled, c.IconUrl, c.SupportedLanguages))
@@ -86,7 +85,7 @@ public class MangaConnectorController(MangaContext context) : Controller
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public async Task<Results<Ok, NotFound<string>, InternalServerError<string>>> SetEnabled(string MangaConnectorName, bool Enabled)
{
if(Tranga.MangaConnectors.FirstOrDefault(c => c.Name.Equals(MangaConnectorName, StringComparison.InvariantCultureIgnoreCase)) is not { } connector)
if(!Tranga.TryGetMangaConnector(MangaConnectorName, out MangaConnectors.MangaConnector? connector))
return TypedResults.NotFound(nameof(MangaConnectorName));
connector.Enabled = Enabled;

View File

@@ -209,10 +209,10 @@ public class MangaController(MangaContext context) : Controller
string cache = CoverSize switch
{
MangaController.CoverSize.Small => TrangaSettings.coverImageCacheSmall,
MangaController.CoverSize.Medium => TrangaSettings.coverImageCacheMedium,
MangaController.CoverSize.Large => TrangaSettings.coverImageCacheLarge,
_ => TrangaSettings.coverImageCacheOriginal
MangaController.CoverSize.Small => TrangaSettings.CoverImageCacheSmall,
MangaController.CoverSize.Medium => TrangaSettings.CoverImageCacheMedium,
MangaController.CoverSize.Large => TrangaSettings.CoverImageCacheLarge,
_ => TrangaSettings.CoverImageCacheOriginal
};
if (await manga.GetCoverImage(cache, HttpContext.RequestAborted) is not { } data)

View File

@@ -47,7 +47,7 @@ public abstract class MangaConnector(string name, string[] supportedLanguages, s
//https?:\/\/[a-zA-Z0-9-]+\.([a-zA-Z0-9-]+\.[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+)) for only second level domains
Match match = urlRex.Match(mangaId.Obj.CoverUrl);
string filename = $"{match.Groups[1].Value}-{mangaId.ObjId}.{mangaId.MangaConnectorName}.{match.Groups[3].Value}";
string saveImagePath = Path.Join(TrangaSettings.coverImageCacheOriginal, filename);
string saveImagePath = Path.Join(TrangaSettings.CoverImageCacheOriginal, filename);
if (File.Exists(saveImagePath))
return filename;
@@ -61,24 +61,24 @@ public abstract class MangaConnector(string name, string[] supportedLanguages, s
using MemoryStream ms = new();
coverResult.result.CopyTo(ms);
byte[] imageBytes = ms.ToArray();
Directory.CreateDirectory(TrangaSettings.coverImageCacheOriginal);
Directory.CreateDirectory(TrangaSettings.CoverImageCacheOriginal);
File.WriteAllBytes(saveImagePath, imageBytes);
using Image image = Image.Load(imageBytes);
Directory.CreateDirectory(TrangaSettings.coverImageCacheLarge);
Directory.CreateDirectory(TrangaSettings.CoverImageCacheLarge);
using Image large = image.Clone(x => x.Resize(new ResizeOptions
{ Size = Constants.ImageLgSize, Mode = ResizeMode.Max }));
large.SaveAsJpeg(Path.Join(TrangaSettings.coverImageCacheLarge, filename), new (){ Quality = 40 });
large.SaveAsJpeg(Path.Join(TrangaSettings.CoverImageCacheLarge, filename), new (){ Quality = 40 });
Directory.CreateDirectory(TrangaSettings.coverImageCacheMedium);
Directory.CreateDirectory(TrangaSettings.CoverImageCacheMedium);
using Image medium = image.Clone(x => x.Resize(new ResizeOptions
{ Size = Constants.ImageMdSize, Mode = ResizeMode.Max }));
medium.SaveAsJpeg(Path.Join(TrangaSettings.coverImageCacheMedium, filename), new (){ Quality = 40 });
medium.SaveAsJpeg(Path.Join(TrangaSettings.CoverImageCacheMedium, filename), new (){ Quality = 40 });
Directory.CreateDirectory(TrangaSettings.coverImageCacheSmall);
Directory.CreateDirectory(TrangaSettings.CoverImageCacheSmall);
using Image small = image.Clone(x => x.Resize(new ResizeOptions
{ Size = Constants.ImageSmSize, Mode = ResizeMode.Max }));
small.SaveAsJpeg(Path.Join(TrangaSettings.coverImageCacheSmall, filename), new (){ Quality = 40 });
small.SaveAsJpeg(Path.Join(TrangaSettings.CoverImageCacheSmall, filename), new (){ Quality = 40 });
}
catch (Exception e)
{

View File

@@ -109,7 +109,7 @@ using (IServiceScope scope = app.Services.CreateScope())
context.Database.Migrate();
if (!context.FileLibraries.Any())
context.FileLibraries.Add(new FileLibrary(Tranga.Settings.DownloadLocation, "Default FileLibrary"));
context.FileLibraries.Add(new (Tranga.Settings.DefaultDownloadLocation, "Default FileLibrary"));
await context.Sync(CancellationToken.None);
}
@@ -135,7 +135,7 @@ using (IServiceScope scope = app.Services.CreateScope())
await context.Sync(CancellationToken.None);
}
Tranga.SetServiceProvider(app.Services);
Tranga.ServiceProvider = app.Services;
Tranga.StartLogger(new FileInfo("Log4Net.config.xml"));
Tranga.AddDefaultWorkers();

View File

@@ -16,7 +16,7 @@ namespace API;
public static class Tranga
{
private static IServiceProvider? _serviceProvider;
internal static IServiceProvider? ServiceProvider { get; set; }
private static readonly ILog Log = LogManager.GetLogger(typeof(Tranga));
internal static readonly MetadataFetcher[] MetadataFetchers = [new MyAnimeList()];
@@ -57,11 +57,6 @@ public static class Tranga
AddWorker(CleanupMangaconnectorIdsWithoutConnector);
}
internal static void SetServiceProvider(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
internal static bool TryGetMangaConnector(string name, [NotNullWhen(true)]out MangaConnector? mangaConnector)
{
mangaConnector =
@@ -82,12 +77,12 @@ public static class Tranga
private static void AddPeriodicWorker(BaseWorker worker, IPeriodic periodic)
{
Log.Debug($"Adding Periodic {worker}");
Task periodicTask = PeriodicTask(worker, periodic);
Task periodicTask = RefreshedPeriodicTask(worker, periodic);
PeriodicWorkers.TryAdd((worker as IPeriodic)!, periodicTask);
periodicTask.Start();
}
private static Task PeriodicTask(BaseWorker worker, IPeriodic periodic) => new (() =>
private static Task RefreshedPeriodicTask(BaseWorker worker, IPeriodic periodic) => new (() =>
{
Log.Debug($"Waiting {periodic.Interval} for next run of {worker}");
Thread.Sleep(periodic.Interval);
@@ -97,9 +92,12 @@ public static class Tranga
private static Action RefreshTask(BaseWorker worker, IPeriodic periodic) => () =>
{
if (worker.State < WorkerExecutionState.Created) //Failed
{
Log.Debug($"Task {worker} failed. Not refreshing.");
return;
}
Log.Debug($"Refreshing {worker}");
Task periodicTask = PeriodicTask(worker, periodic);
Task periodicTask = RefreshedPeriodicTask(worker, periodic);
PeriodicWorkers.AddOrUpdate((worker as IPeriodic)!, periodicTask, (_, _) => periodicTask);
periodicTask.Start();
};
@@ -113,15 +111,15 @@ public static class Tranga
private static readonly ConcurrentDictionary<BaseWorker, Task<BaseWorker[]>> RunningWorkers = new();
public static BaseWorker[] GetRunningWorkers() => RunningWorkers.Keys.ToArray();
internal static void StartWorker(BaseWorker worker, Action? callback = null)
internal static void StartWorker(BaseWorker worker, Action? finishedCallback = null)
{
Log.Debug($"Starting {worker}");
if (_serviceProvider is null)
if (ServiceProvider is null)
{
Log.Fatal("ServiceProvider is null");
return;
}
Action afterWorkCallback = AfterWork(worker, callback);
Action afterWorkCallback = DefaultAfterWork(worker, finishedCallback);
while (RunningWorkers.Count > Settings.MaxConcurrentWorkers)
{
@@ -131,23 +129,23 @@ public static class Tranga
if (worker is BaseWorkerWithContext<MangaContext> mangaContextWorker)
{
mangaContextWorker.SetScope(_serviceProvider.CreateScope());
mangaContextWorker.SetScope(ServiceProvider.CreateScope());
RunningWorkers.TryAdd(mangaContextWorker, mangaContextWorker.DoWork(afterWorkCallback));
}else if (worker is BaseWorkerWithContext<NotificationsContext> notificationContextWorker)
{
notificationContextWorker.SetScope(_serviceProvider.CreateScope());
notificationContextWorker.SetScope(ServiceProvider.CreateScope());
RunningWorkers.TryAdd(notificationContextWorker, notificationContextWorker.DoWork(afterWorkCallback));
}else if (worker is BaseWorkerWithContext<LibraryContext> libraryContextWorker)
{
libraryContextWorker.SetScope(_serviceProvider.CreateScope());
libraryContextWorker.SetScope(ServiceProvider.CreateScope());
RunningWorkers.TryAdd(libraryContextWorker, libraryContextWorker.DoWork(afterWorkCallback));
}else
RunningWorkers.TryAdd(worker, worker.DoWork(afterWorkCallback));
}
private static Action AfterWork(BaseWorker worker, Action? callback = null) => () =>
private static Action DefaultAfterWork(BaseWorker worker, Action? callback = null) => () =>
{
Log.Debug($"AfterWork {worker}");
Log.Debug($"DefaultAfterWork {worker}");
if (RunningWorkers.TryGetValue(worker, out Task<BaseWorker[]>? task))
{
Log.Debug($"Waiting for Children to exit {worker}");

View File

@@ -7,22 +7,19 @@ namespace API;
public struct TrangaSettings()
{
[JsonIgnore]
public static string workingDirectory => Path.Join(RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/usr/share" : Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "tranga-api");
[JsonIgnore]
public static string settingsFilePath => Path.Join(workingDirectory, "settings.json");
[JsonIgnore] public static string coverImageCache => Path.Join(workingDirectory, "imageCache");
[JsonIgnore] public static string coverImageCacheOriginal => Path.Join(coverImageCache, "original");
[JsonIgnore] public static string coverImageCacheLarge => Path.Join(coverImageCache, "large");
[JsonIgnore] public static string coverImageCacheMedium => Path.Join(coverImageCache, "medium");
[JsonIgnore] public static string coverImageCacheSmall => Path.Join(coverImageCache, "small");
public string DownloadLocation => RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/Manga" : Path.Join(Directory.GetCurrentDirectory(), "Manga");
[JsonIgnore]
internal static readonly string DefaultUserAgent = $"Tranga/2.0 ({Enum.GetName(Environment.OSVersion.Platform)}; {(Environment.Is64BitOperatingSystem ? "x64" : "")})";
[JsonIgnore] public static string WorkingDirectory => Path.Join(RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/usr/share" : Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "tranga-api");
[JsonIgnore] public static string SettingsFilePath => Path.Join(WorkingDirectory, "settings.json");
[JsonIgnore] public static string CoverImageCache => Path.Join(WorkingDirectory, "imageCache");
[JsonIgnore] public static string CoverImageCacheOriginal => Path.Join(CoverImageCache, "original");
[JsonIgnore] public static string CoverImageCacheLarge => Path.Join(CoverImageCache, "large");
[JsonIgnore] public static string CoverImageCacheMedium => Path.Join(CoverImageCache, "medium");
[JsonIgnore] public static string CoverImageCacheSmall => Path.Join(CoverImageCache, "small");
public string DefaultDownloadLocation => Environment.GetEnvironmentVariable("DOWNLOAD_LOCATION") ?? (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/Manga" : Path.Join(Directory.GetCurrentDirectory(), "Manga"));
[JsonIgnore] internal static readonly string DefaultUserAgent = $"Tranga/2.0 ({Enum.GetName(Environment.OSVersion.Platform)}; {(Environment.Is64BitOperatingSystem ? "x64" : "")})";
public string UserAgent { get; set; } = DefaultUserAgent;
public int ImageCompression{ get; set; } = 40;
public bool BlackWhiteImages { get; set; } = false;
public string FlareSolverrUrl { get; set; } = string.Empty;
public string FlareSolverrUrl { get; set; } = Environment.GetEnvironmentVariable("FLARESOLVERR_URL") ?? string.Empty;
/// <summary>
/// Placeholders:
/// %M Obj Name
@@ -53,20 +50,20 @@ public struct TrangaSettings()
public string DownloadLanguage { get; set; } = "en";
public int MaxConcurrentDownloads { get; set; } = 5;
public int MaxConcurrentDownloads { get; set; } = (int)Math.Max(Environment.ProcessorCount * 0.75, 1); // Minimum of 1 Tasks, maximum of 0.75 per Core
public int MaxConcurrentWorkers { get; set; } = 10;
public int MaxConcurrentWorkers { get; set; } = Math.Max(Environment.ProcessorCount, 4); // Minimum of 4 Tasks, maximum of 1 per Core
public static TrangaSettings Load()
{
if (!File.Exists(settingsFilePath))
if (!File.Exists(SettingsFilePath))
new TrangaSettings().Save();
return JsonConvert.DeserializeObject<TrangaSettings>(File.ReadAllText(settingsFilePath));
return JsonConvert.DeserializeObject<TrangaSettings>(File.ReadAllText(SettingsFilePath));
}
public void Save()
{
File.WriteAllText(settingsFilePath, JsonConvert.SerializeObject(this, Formatting.Indented));
File.WriteAllText(SettingsFilePath, JsonConvert.SerializeObject(this, Formatting.Indented));
}
public void SetUserAgent(string value)
@@ -122,4 +119,10 @@ public struct TrangaSettings()
this.MaxConcurrentDownloads = value;
Save();
}
public void SetMaxConcurrentWorkers(int value)
{
this.MaxConcurrentWorkers = value;
Save();
}
}

View File

@@ -207,7 +207,7 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId<Chapter> c
return;
}
string fullCoverPath = Path.Join(TrangaSettings.coverImageCacheOriginal, coverFileNameInCache);
string fullCoverPath = Path.Join(TrangaSettings.CoverImageCacheOriginal, coverFileNameInCache);
string newFilePath = Path.Join(publicationFolder, $"cover.{Path.GetFileName(coverFileNameInCache).Split('.')[^1]}" );
File.Copy(fullCoverPath, newFilePath, true);
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))

View File

@@ -12,10 +12,10 @@ public class CleanupMangaCoversWorker(TimeSpan? interval = null, IEnumerable<Bas
{
Log.Info("Removing stale files...");
string[] usedFiles = DbContext.Mangas.Select(m => m.CoverFileNameInCache).Where(s => s != null).ToArray()!;
CleanupImageCache(usedFiles, TrangaSettings.coverImageCacheOriginal);
CleanupImageCache(usedFiles, TrangaSettings.coverImageCacheLarge);
CleanupImageCache(usedFiles, TrangaSettings.coverImageCacheMedium);
CleanupImageCache(usedFiles, TrangaSettings.coverImageCacheSmall);
CleanupImageCache(usedFiles, TrangaSettings.CoverImageCacheOriginal);
CleanupImageCache(usedFiles, TrangaSettings.CoverImageCacheLarge);
CleanupImageCache(usedFiles, TrangaSettings.CoverImageCacheMedium);
CleanupImageCache(usedFiles, TrangaSettings.CoverImageCacheSmall);
return new Task<BaseWorker[]>(() => []);
}

View File

@@ -18,7 +18,7 @@ public class CleanupMangaconnectorIdsWithoutConnector : BaseWorkerWithContext<Ma
.Where(mcId => connectorNames.Any(name => name == mcId.MangaConnectorName)).ToListAsync() is
{ Count: > 0 } list)
{
string filePath = Path.Join(TrangaSettings.workingDirectory, $"deletedManga-{DateTime.UtcNow.Ticks}.txt");
string filePath = Path.Join(TrangaSettings.WorkingDirectory, $"deletedManga-{DateTime.UtcNow.Ticks}.txt");
Log.Debug($"Writing deleted manga to {filePath}.");
await File.WriteAllLinesAsync(filePath, list.Select(id => string.Join('-', id.MangaConnectorName, id.IdOnConnectorSite, id.Obj.Name, id.WebsiteUrl)), CancellationToken);
}

View File

@@ -125,6 +125,15 @@ downloaded (where Komga/Kavita can access them for example).
The file also includes [tranga-website](https://github.com/C9Glax/tranga-website) as frontend. For its configuration refer to the
[Tranga-Website Repository](https://github.com/C9Glax/tranga-website) README.
| env-var | default-value |
|-------------------|------------------|
| POSTGRES_HOST | `tranga-pg:5432` |
| POSTGRES_DB | `postgres` |
| POSTGRES_USER | `postgres` |
| POSTGRES_PASSWORD | `postgres` |
| DOWNLOAD_LOCATION | `/Manga` |
| FLARESOLVERR_URL | `<empty>` |
For compatibility do not execute the compose as root (which you should not do anyways...) but as user that can
access the folder. Permission conflicts with Komga and Kavita should thus be limited.