Compare commits

..

5 Commits

11 changed files with 308 additions and 15 deletions

View File

@ -57,6 +57,11 @@ public abstract class GlobalBase
File.WriteAllText(settings.notificationConnectorsFilePath, JsonConvert.SerializeObject(notificationConnectors)); File.WriteAllText(settings.notificationConnectorsFilePath, JsonConvert.SerializeObject(notificationConnectors));
} }
protected void DeleteNotificationConnector(NotificationConnector.NotificationManagerType notificationManagerType)
{
notificationConnectors.RemoveWhere(nc => nc.notificationManagerType == notificationManagerType);
}
protected void UpdateLibraries() protected void UpdateLibraries()
{ {
foreach(LibraryConnector lc in libraryConnectors) foreach(LibraryConnector lc in libraryConnectors)
@ -73,6 +78,11 @@ public abstract class GlobalBase
File.WriteAllText(settings.libraryConnectorsFilePath, JsonConvert.SerializeObject(libraryConnectors)); File.WriteAllText(settings.libraryConnectorsFilePath, JsonConvert.SerializeObject(libraryConnectors));
} }
protected void DeleteLibraryConnector(LibraryConnector.LibraryType libraryType)
{
libraryConnectors.RemoveWhere(lc => lc.libraryType == libraryType);
}
protected bool IsFileInUse(string filePath) protected bool IsFileInUse(string filePath)
{ {
if (!File.Exists(filePath)) if (!File.Exists(filePath))

View File

@ -7,7 +7,7 @@ public class DownloadNewChapters : Job
{ {
public Publication publication { get; init; } public Publication publication { get; init; }
public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Publication publication, bool recurring = false) : base (clone, connector, recurring) public DownloadNewChapters(GlobalBase clone, MangaConnector connector, Publication publication, bool recurring = false, TimeSpan? recurrence = null) : base (clone, connector, recurring, recurrence)
{ {
this.publication = publication; this.publication = publication;
} }

View File

@ -24,6 +24,45 @@ public class JobBoss : GlobalBase
this.jobs.Remove(job); this.jobs.Remove(job);
} }
public void RemoveJobs(IEnumerable<Job> jobs)
{
foreach (Job job in jobs)
{
job.Cancel();
this.jobs.Remove(job);
}
}
public IEnumerable<Job> GetJobsLike(string? connectorName = null, string? internalId = null, string? chapterNumber = null)
{
IEnumerable<Job> ret = this.jobs;
if (connectorName is not null)
ret = ret.Where(job => job.mangaConnector.name == connectorName);
if (internalId is not null && chapterNumber is not null)
ret = ret.Where(jjob =>
{
if (jjob is not DownloadChapter job)
return false;
return job.chapter.parentPublication.internalId == internalId &&
job.chapter.chapterNumber == chapterNumber;
});
else if (internalId is not null)
ret = ret.Where(jjob =>
{
if (jjob is not DownloadNewChapters job)
return false;
return job.publication.internalId == internalId;
});
return ret;
}
public IEnumerable<Job> GetJobsLike(MangaConnector? mangaConnector = null, Publication? publication = null,
Chapter? chapter = null)
{
return GetJobsLike(mangaConnector?.name, publication?.internalId, chapter?.chapterNumber);
}
private bool QueueContainsJob(Job job) private bool QueueContainsJob(Job job)
{ {
mangaConnectorJobQueue.TryAdd(job.mangaConnector, new Queue<Job>()); mangaConnectorJobQueue.TryAdd(job.mangaConnector, new Queue<Job>());

View File

@ -7,13 +7,13 @@ namespace Tranga.LibraryConnectors;
public class Kavita : LibraryConnector public class Kavita : LibraryConnector
{ {
public Kavita(string baseUrl, string username, string password, GlobalBase clone) : public Kavita(GlobalBase clone, string baseUrl, string username, string password) :
base(baseUrl, GetToken(baseUrl, username, password), LibraryType.Kavita, clone) base(clone, baseUrl, GetToken(baseUrl, username, password), LibraryType.Kavita)
{ {
} }
[JsonConstructor] [JsonConstructor]
public Kavita(string baseUrl, string auth, GlobalBase clone) : base(baseUrl, auth, LibraryType.Kavita, clone) public Kavita(GlobalBase clone, string baseUrl, string auth) : base(clone, baseUrl, auth, LibraryType.Kavita)
{ {
} }

View File

@ -10,13 +10,13 @@ namespace Tranga.LibraryConnectors;
/// </summary> /// </summary>
public class Komga : LibraryConnector public class Komga : LibraryConnector
{ {
public Komga(string baseUrl, string username, string password, GlobalBase clone) public Komga(GlobalBase clone, string baseUrl, string username, string password)
: base(baseUrl, Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{username}:{password}")), LibraryType.Komga, clone) : base(clone, baseUrl, Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{username}:{password}")), LibraryType.Komga)
{ {
} }
[JsonConstructor] [JsonConstructor]
public Komga(string baseUrl, string auth, GlobalBase clone) : base(baseUrl, auth, LibraryType.Komga, clone) public Komga(GlobalBase clone, string baseUrl, string auth) : base(clone, baseUrl, auth, LibraryType.Komga)
{ {
} }

View File

@ -18,7 +18,7 @@ public abstract class LibraryConnector : GlobalBase
// ReSharper disable once MemberCanBeProtected.Global // ReSharper disable once MemberCanBeProtected.Global
public string auth { get; } //Base64 encoded, if you use your password everywhere, you have problems public string auth { get; } //Base64 encoded, if you use your password everywhere, you have problems
protected LibraryConnector(string baseUrl, string auth, LibraryType libraryType, GlobalBase clone) : base(clone) protected LibraryConnector(GlobalBase clone, string baseUrl, string auth, LibraryType libraryType) : base(clone)
{ {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
this.auth = auth; this.auth = auth;

View File

@ -11,7 +11,7 @@ public class Gotify : NotificationConnector
private readonly HttpClient _client = new(); private readonly HttpClient _client = new();
[JsonConstructor] [JsonConstructor]
public Gotify(string endpoint, string appToken, GlobalBase clone) : base(NotificationManagerType.Gotify, clone) public Gotify(GlobalBase clone, string endpoint, string appToken) : base(clone, NotificationManagerType.Gotify)
{ {
this.endpoint = endpoint; this.endpoint = endpoint;
this.appToken = appToken; this.appToken = appToken;

View File

@ -10,7 +10,7 @@ public class LunaSea : NotificationConnector
private readonly HttpClient _client = new(); private readonly HttpClient _client = new();
[JsonConstructor] [JsonConstructor]
public LunaSea(string id, GlobalBase clone) : base(NotificationManagerType.LunaSea, clone) public LunaSea(GlobalBase clone, string id) : base(clone, NotificationManagerType.LunaSea)
{ {
this.id = id; this.id = id;
} }

View File

@ -4,7 +4,7 @@ public abstract class NotificationConnector : GlobalBase
{ {
public NotificationManagerType notificationManagerType; public NotificationManagerType notificationManagerType;
protected NotificationConnector(NotificationManagerType notificationManagerType, GlobalBase clone) : base(clone) protected NotificationConnector(GlobalBase clone, NotificationManagerType notificationManagerType) : base(clone)
{ {
this.notificationManagerType = notificationManagerType; this.notificationManagerType = notificationManagerType;
} }

View File

@ -4,7 +4,9 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Newtonsoft.Json; using Newtonsoft.Json;
using Tranga.Jobs; using Tranga.Jobs;
using Tranga.LibraryConnectors;
using Tranga.MangaConnectors; using Tranga.MangaConnectors;
using Tranga.NotificationConnectors;
namespace Tranga; namespace Tranga;
@ -155,9 +157,15 @@ public class Server : GlobalBase
case "NotificationConnectors": case "NotificationConnectors":
SendResponse(HttpStatusCode.OK, response, notificationConnectors); SendResponse(HttpStatusCode.OK, response, notificationConnectors);
break; break;
case "NotificationsConnectors/Types":
SendResponse(HttpStatusCode.OK, response, Enum.GetNames(typeof(NotificationConnectors.NotificationConnector.NotificationManagerType)));
break;
case "LibraryConnectors": case "LibraryConnectors":
SendResponse(HttpStatusCode.OK, response, libraryConnectors); SendResponse(HttpStatusCode.OK, response, libraryConnectors);
break; break;
case "LibraryConnectors/Types":
SendResponse(HttpStatusCode.OK, response, Enum.GetNames(typeof(LibraryConnectors.LibraryConnector.LibraryType)));
break;
default: default:
SendResponse(HttpStatusCode.BadRequest, response); SendResponse(HttpStatusCode.BadRequest, response);
break; break;
@ -166,12 +174,217 @@ public class Server : GlobalBase
private void HandlePost(HttpListenerRequest request, Stream content, HttpListenerResponse response) private void HandlePost(HttpListenerRequest request, Stream content, HttpListenerResponse response)
{ {
Dictionary<string, string> requestVariables = GetRequestVariables(request.Url!.Query);
string? connectorName, internalId;
MangaConnector connector;
Publication publication;
string path = Regex.Match(request.Url!.LocalPath, @"[A-z0-9]+(\/[A-z0-9]+)*").Value;
switch (path)
{
case "Tasks/MonitorManga":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) ||
!requestVariables.TryGetValue("interval", out string? intervalStr) ||
_parent.GetConnector(connectorName) is null ||
_parent.GetPublicationById(internalId) is null ||
!TimeSpan.TryParse(intervalStr, out TimeSpan interval))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
connector = _parent.GetConnector(connectorName)!;
publication = (Publication)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.AddJob(new DownloadNewChapters(this, connector, publication, true, interval));
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Tasks/DownloadNewChapters":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) ||
_parent.GetConnector(connectorName) is null ||
_parent.GetPublicationById(internalId) is null)
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
connector = _parent.GetConnector(connectorName)!;
publication = (Publication)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.AddJob(new DownloadNewChapters(this, connector, publication, false));
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Settings/UpdateDownloadLocation":
if (!requestVariables.TryGetValue("downloadLocation", out string? downloadLocation) ||
!requestVariables.TryGetValue("moveFiles", out string? moveFilesStr) ||
!Boolean.TryParse(moveFilesStr, out bool moveFiles))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
settings.UpdateDownloadLocation(downloadLocation, moveFiles);
SendResponse(HttpStatusCode.Accepted, response);
break;
/*case "Settings/UpdateWorkingDirectory":
if (!requestVariables.TryGetValue("workingDirectory", out string? workingDirectory))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
settings.UpdateWorkingDirectory(workingDirectory);
SendResponse(HttpStatusCode.Accepted, response);
break;*/
case "NotificationConnectors/Update":
if (!requestVariables.TryGetValue("notificationConnector", out string? notificationConnectorStr) ||
!Enum.TryParse(notificationConnectorStr, out NotificationConnector.NotificationManagerType notificationManagerType))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
if (notificationManagerType is NotificationConnector.NotificationManagerType.Gotify)
{
if (!requestVariables.TryGetValue("gotifyUrl", out string? gotifyUrl) ||
!requestVariables.TryGetValue("gotifyAppToken", out string? gotifyAppToken))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
AddNotificationConnector(new Gotify(this, gotifyUrl, gotifyAppToken));
SendResponse(HttpStatusCode.Accepted, response);
break;
}
if (notificationManagerType is NotificationConnector.NotificationManagerType.LunaSea)
{
if (!requestVariables.TryGetValue("lunaseaWebhook", out string? lunaseaWebhook))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
AddNotificationConnector(new LunaSea(this, lunaseaWebhook));
SendResponse(HttpStatusCode.Accepted, response);
break;
}
break;
case "LibraryManagers/Update":
if (!requestVariables.TryGetValue("libraryManager", out string? libraryManagerStr) ||
!Enum.TryParse(libraryManagerStr,
out LibraryConnectors.LibraryConnector.LibraryType libraryManagerType))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
if (libraryManagerType is LibraryConnector.LibraryType.Kavita)
{
if (!requestVariables.TryGetValue("kavitaUrl", out string? kavitaUrl) ||
!requestVariables.TryGetValue("kavitaUsername", out string? kavitaUsername) ||
!requestVariables.TryGetValue("kavitaPassword", out string? kavitaPassword))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
AddLibraryConnector(new Kavita(this, kavitaUrl, kavitaUsername, kavitaPassword));
SendResponse(HttpStatusCode.Accepted, response);
break;
}
if (libraryManagerType is LibraryConnector.LibraryType.Komga)
{
if (!requestVariables.TryGetValue("komgaUrl", out string? komgaUrl) ||
!requestVariables.TryGetValue("komgaAuth", out string? komgaAuth))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
AddLibraryConnector(new Komga(this, komgaUrl, komgaAuth));
SendResponse(HttpStatusCode.Accepted, response);
break;
}
break;
default:
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
} }
private void HandleDelete(HttpListenerRequest request, Stream content, HttpListenerResponse response) private void HandleDelete(HttpListenerRequest request, Stream content, HttpListenerResponse response)
{ {
Dictionary<string, string> requestVariables = GetRequestVariables(request.Url!.Query);
string? connectorName, internalId;
MangaConnector connector;
Publication publication;
string path = Regex.Match(request.Url!.LocalPath, @"[A-z0-9]+(\/[A-z0-9]+)*").Value;
switch (path)
{
case "Tasks/DownloadChapter":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) ||
!requestVariables.TryGetValue("chapterNumber", out string? chapterNumber) ||
_parent.GetConnector(connectorName) is null ||
_parent.GetPublicationById(internalId) is null)
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
connector = _parent.GetConnector(connectorName)!;
publication = (Publication)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.RemoveJobs(_parent._jobBoss.GetJobsLike(connectorName, internalId, chapterNumber));
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Tasks/MonitorManga":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) ||
!requestVariables.TryGetValue("interval", out string? intervalStr) ||
_parent.GetConnector(connectorName) is null ||
_parent.GetPublicationById(internalId) is null ||
!TimeSpan.TryParse(intervalStr, out TimeSpan interval))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
connector = _parent.GetConnector(connectorName)!;
publication = (Publication)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.RemoveJobs(_parent._jobBoss.GetJobsLike(connector, publication));
SendResponse(HttpStatusCode.Accepted, response);
break;
case "Tasks/DownloadNewChapters":
if(!requestVariables.TryGetValue("connector", out connectorName) ||
!requestVariables.TryGetValue("internalId", out internalId) ||
_parent.GetConnector(connectorName) is null ||
_parent.GetPublicationById(internalId) is null)
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
connector = _parent.GetConnector(connectorName)!;
publication = (Publication)_parent.GetPublicationById(internalId)!;
_parent._jobBoss.RemoveJobs(_parent._jobBoss.GetJobsLike(connector, publication));
SendResponse(HttpStatusCode.Accepted, response);
break;
case "NotificationConnectors":
if (!requestVariables.TryGetValue("notificationConnector", out string? notificationConnectorStr) ||
!Enum.TryParse(notificationConnectorStr, out NotificationConnector.NotificationManagerType notificationManagerType))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
DeleteNotificationConnector(notificationManagerType);
SendResponse(HttpStatusCode.Accepted, response);
break;
case "LibraryManagers":
if (!requestVariables.TryGetValue("libraryManager", out string? libraryManagerStr) ||
!Enum.TryParse(libraryManagerStr,
out LibraryConnectors.LibraryConnector.LibraryType libraryManagerType))
{
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
DeleteLibraryConnector(libraryManagerType);
SendResponse(HttpStatusCode.Accepted, response);
break;
default:
SendResponse(HttpStatusCode.BadRequest, response);
break;
}
} }
private void SendResponse(HttpStatusCode statusCode, HttpListenerResponse response, object? content = null) private void SendResponse(HttpStatusCode statusCode, HttpListenerResponse response, object? content = null)

View File

@ -1,14 +1,16 @@
using Logging; using System.Runtime.InteropServices;
using Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Tranga.LibraryConnectors; using Tranga.LibraryConnectors;
using Tranga.NotificationConnectors; using Tranga.NotificationConnectors;
using static System.IO.UnixFileMode;
namespace Tranga; namespace Tranga;
public class TrangaSettings public class TrangaSettings
{ {
public string downloadLocation { get; private set; } public string downloadLocation { get; private set; }
public string workingDirectory { get; init; } public string workingDirectory { get; private set; }
public int apiPortNumber { get; init; } public int apiPortNumber { get; init; }
[JsonIgnore] public string settingsFilePath => Path.Join(workingDirectory, "settings.json"); [JsonIgnore] public string settingsFilePath => Path.Join(workingDirectory, "settings.json");
[JsonIgnore] public string libraryConnectorsFilePath => Path.Join(workingDirectory, "libraryConnectors.json"); [JsonIgnore] public string libraryConnectorsFilePath => Path.Join(workingDirectory, "libraryConnectors.json");
@ -34,6 +36,8 @@ public class TrangaSettings
this.downloadLocation = settings.downloadLocation; this.downloadLocation = settings.downloadLocation;
this.workingDirectory = settings.workingDirectory; this.workingDirectory = settings.workingDirectory;
} }
UpdateDownloadLocation(this.downloadLocation, false);
} }
public HashSet<LibraryConnector> LoadLibraryConnectors() public HashSet<LibraryConnector> LoadLibraryConnectors()
@ -64,6 +68,33 @@ public class TrangaSettings
})!; })!;
} }
public void UpdateDownloadLocation(string newPath, bool moveFiles = true)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
Directory.CreateDirectory(newPath,
GroupRead | GroupWrite | None | OtherRead | OtherWrite | UserRead | UserWrite);
else
Directory.CreateDirectory(newPath);
if (moveFiles && Directory.Exists(this.downloadLocation))
Directory.Move(this.downloadLocation, newPath);
this.downloadLocation = newPath;
ExportSettings();
}
public void UpdateWorkingDirectory(string newPath)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
Directory.CreateDirectory(newPath,
GroupRead | GroupWrite | None | OtherRead | OtherWrite | UserRead | UserWrite);
else
Directory.CreateDirectory(newPath);
Directory.Move(this.workingDirectory, newPath);
this.workingDirectory = newPath;
ExportSettings();
}
public void ExportSettings() public void ExportSettings()
{ {
while (File.Exists(settingsFilePath)) while (File.Exists(settingsFilePath))