mirror of
https://github.com/C9Glax/tranga.git
synced 2025-09-18 07:39:13 +02:00
Add more Environment Variables
This commit is contained in:
@@ -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;
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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}");
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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))
|
||||
|
@@ -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[]>(() => []);
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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.
|
||||
|
||||
|
Reference in New Issue
Block a user