13 Commits
1.4 ... 1.4.1

Author SHA1 Message Date
c685bd622f Website:
New task-Creation dialog
Redesigned Settings dialog
2023-06-08 19:25:28 +02:00
dc83cc2194 Fixed Range on CLI downloadchaptertask creation 2023-06-08 19:25:03 +02:00
7784f2024e API changes:
/Tranga/GetAvailableControllers => /Controllers/Get
/Tranga/GetKnownPublications =>/Publications/GetKnown
/Tranga/GetPublicationsFromConnector => /Publications/GetFromConnector
/Tasks/GetTaskTypes => /Tasks/GetTypes
/Tasks/GetTaskProgress => /Tasks/GetProgress
/Tasks/Create is now split in 3:
    /Tasks/CreateMonitorTask
    /Tasks/CreateUpdateLibraryTask
    /Tasks/CreateDownloadChaptersTask
2023-06-08 19:24:46 +02:00
4895079887 Remove DownloadChapterTask from _runningDownloadChapterTasks after completion 2023-06-07 15:01:24 +02:00
ab1ddc6dc8 Less cluttered log 2023-06-07 00:31:27 +02:00
87eade10cf #40 task timeout criteria 2023-06-07 00:27:53 +02:00
1f3ac41b30 removed unnecessary cast 2023-06-07 00:24:58 +02:00
6a304bb330 #40 task timeout 2023-06-07 00:24:27 +02:00
b0642d1251 removed unnecessary check 2023-06-06 22:11:57 +02:00
63b5139e93 Split error message for better logging 2023-06-06 22:11:38 +02:00
e938784388 Created own base image for tranga-api (to stop apt always updating) 2023-06-06 22:11:26 +02:00
c436389426 renamed wrong variable names publicationId -> internalId 2023-06-06 21:57:10 +02:00
5099e25f3f Fixed wrong comparison on add new task 2023-06-06 21:56:51 +02:00
13 changed files with 434 additions and 229 deletions

View File

@ -6,9 +6,9 @@ COPY . /src/
RUN dotnet restore Tranga-API/Tranga-API.csproj
RUN dotnet publish -c Release -o /publish
FROM mcr.microsoft.com/dotnet/aspnet:7.0 as runtime
#FROM mcr.microsoft.com/dotnet/aspnet:7.0 as runtime
FROM glax/tranga-base:latest as runtime
WORKDIR /publish
COPY --from=build-env /publish .
EXPOSE 80
RUN apt-get update && apt-get install -y libx11-6 libx11-xcb1 libatk1.0-0 libgtk-3-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 libxshmfence1 libnss3
ENTRYPOINT ["dotnet", "/publish/Tranga-API.dll"]

4
Dockerfile-base Normal file
View File

@ -0,0 +1,4 @@
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/aspnet:7.0 as runtime
WORKDIR /publish
RUN apt-get update && apt-get install -y libx11-6 libx11-xcb1 libatk1.0-0 libgtk-3-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 libxshmfence1 libnss3

View File

@ -1,6 +1,7 @@
using System.Runtime.InteropServices;
using Logging;
using Tranga;
using Tranga.TrangaTasks;
string applicationFolderPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Tranga-API");
string downloadFolderPath = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/Manga" : Path.Join(applicationFolderPath, "Manga");
@ -54,11 +55,17 @@ app.UseSwaggerUI();
app.UseCors(corsHeader);
app.MapGet("/Tranga/GetAvailableControllers", () => taskManager.GetAvailableConnectors().Keys.ToArray());
app.MapGet("/Controllers/Get", () => taskManager.GetAvailableConnectors().Keys.ToArray());
app.MapGet("/Tranga/GetKnownPublications", () => taskManager.GetAllPublications());
app.MapGet("/Publications/GetKnown", (string? internalId) =>
{
if(internalId is null)
return taskManager.GetAllPublications();
return new [] { taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == internalId) };
});
app.MapGet("/Tranga/GetPublicationsFromConnector", (string connectorName, string title) =>
app.MapGet("/Publications/GetFromConnector", (string connectorName, string title) =>
{
Connector? connector = taskManager.GetAvailableConnectors().FirstOrDefault(con => con.Key == connectorName).Value;
if (connector is null)
@ -68,13 +75,65 @@ app.MapGet("/Tranga/GetPublicationsFromConnector", (string connectorName, string
return taskManager.GetPublicationsFromConnector(connector, title);
});
app.MapGet("/Tasks/GetTaskTypes", () => Enum.GetNames(typeof(TrangaTask.Task)));
app.MapPost("/Tasks/Create", (string taskType, string? connectorName, string? publicationId, string reoccurrenceTime, string? language) =>
app.MapGet("/Publications/GetChapters", (string connectorName, string internalId, string? language) =>
{
TrangaTask.Task task = Enum.Parse<TrangaTask.Task>(taskType);
taskManager.AddTask(task, connectorName, publicationId, TimeSpan.Parse(reoccurrenceTime), language??"");
Connector? connector = taskManager.GetAvailableConnectors().FirstOrDefault(con => con.Key == connectorName).Value;
if (connector is null)
return Array.Empty<Chapter>();
Publication? publication = taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == internalId);
if (publication is null)
return Array.Empty<Chapter>();
return connector.GetChapters((Publication)publication, language??"en");
});
app.MapGet("/Tasks/GetTypes", () => Enum.GetNames(typeof(TrangaTask.Task)));
app.MapPost("/Tasks/CreateMonitorTask",
(string connectorName, string internalId, string reoccurrenceTime, string? language) =>
{
Connector? connector =
taskManager.GetAvailableConnectors().FirstOrDefault(con => con.Key == connectorName).Value;
if (connector is null)
return;
Publication? publication = taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == internalId);
if (publication is null)
return;
taskManager.AddTask(new DownloadNewChaptersTask(TrangaTask.Task.DownloadNewChapters, connectorName,
(Publication)publication,
TimeSpan.Parse(reoccurrenceTime), language ?? "en"));
});
app.MapPost("/Tasks/CreateUpdateLibraryTask", (string reoccurrenceTime) =>
{
taskManager.AddTask(new UpdateLibrariesTask(TrangaTask.Task.UpdateLibraries, TimeSpan.Parse(reoccurrenceTime)));
});
app.MapPost("/Tasks/CreateDownloadChaptersTask", (string connectorName, string internalId, string chapters, string? language) => {
Connector? connector =
taskManager.GetAvailableConnectors().FirstOrDefault(con => con.Key == connectorName).Value;
if (connector is null)
return;
Publication? publication = taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == internalId);
if (publication is null)
return;
Chapter[] availableChapters = connector.GetChapters((Publication)publication, language??"en");;
if (chapters.Contains('-'))
{
int start = Convert.ToInt32(chapters.Split('-')[0]);
int end = Convert.ToInt32(chapters.Split('-')[1]) + 1;
foreach (Chapter chapter in availableChapters[start..end])
{
taskManager.AddTask(new DownloadChapterTask(TrangaTask.Task.DownloadChapter, connectorName,
(Publication)publication, chapter, "en"));
}
}
else
taskManager.AddTask(new DownloadChapterTask(TrangaTask.Task.DownloadChapter, connectorName,
(Publication)publication, availableChapters[Convert.ToInt32(chapters)], "en"));
});
app.MapDelete("/Tasks/Delete", (string taskType, string? connectorName, string? publicationId) =>
@ -96,7 +155,7 @@ app.MapGet("/Tasks/Get", (string taskType, string? connectorName, string? search
}
});
app.MapGet("/Tasks/GetTaskProgress", (string taskType, string? connectorName, string? publicationId) =>
app.MapGet("/Tasks/GetProgress", (string taskType, string? connectorName, string? publicationId) =>
{
try
{

View File

@ -510,7 +510,7 @@ public static class Tranga_Cli
if (selectedChapters.Contains('-'))
{
int start = Convert.ToInt32(selectedChapters.Split('-')[0]);
int end = Convert.ToInt32(selectedChapters.Split('-')[1]);
int end = Convert.ToInt32(selectedChapters.Split('-')[1]) + 1;
return availableChapters[start..end];
}
else

View File

@ -49,9 +49,8 @@ public abstract class LibraryManager
Method = HttpMethod.Get,
RequestUri = new Uri(url)
};
logger?.WriteLine("LibraryManager", $"GET {url}");
HttpResponseMessage response = client.Send(requestMessage);
logger?.WriteLine("LibraryManager", $"{(int)response.StatusCode} {response.StatusCode}: {response.ReasonPhrase}");
logger?.WriteLine("LibraryManager", $"GET {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}");
if(response.StatusCode is HttpStatusCode.Unauthorized && response.RequestMessage!.RequestUri!.AbsoluteUri != url)
return MakeRequest(response.RequestMessage!.RequestUri!.AbsoluteUri, authScheme, auth, logger);
@ -76,9 +75,8 @@ public abstract class LibraryManager
Method = HttpMethod.Post,
RequestUri = new Uri(url)
};
logger?.WriteLine("LibraryManager", $"POST {url}");
HttpResponseMessage response = client.Send(requestMessage);
logger?.WriteLine("LibraryManager", $"{(int)response.StatusCode} {response.StatusCode}: {response.ReasonPhrase}");
logger?.WriteLine("LibraryManager", $"POST {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}");
if(response.StatusCode is HttpStatusCode.Unauthorized && response.RequestMessage!.RequestUri!.AbsoluteUri != url)
return MakePost(response.RequestMessage!.RequestUri!.AbsoluteUri, authScheme, auth, logger);

View File

@ -13,12 +13,14 @@ namespace Tranga;
public class TaskManager
{
public Dictionary<Publication, List<Chapter>> chapterCollection = new();
private HashSet<TrangaTask> _allTasks = new HashSet<TrangaTask>();
private HashSet<TrangaTask> _allTasks = new();
private bool _continueRunning = true;
private readonly Connector[] _connectors;
public TrangaSettings settings { get; }
private Logger? logger { get; }
private readonly Dictionary<DownloadChapterTask, Task> _runningDownloadChapterTasks = new();
/// <param name="downloadFolderPath">Local path to save data (Manga) to</param>
/// <param name="workingDirectory">Path to the working directory</param>
/// <param name="imageCachePath">Path to the cover-image cache</param>
@ -117,6 +119,26 @@ public class TaskManager
break;
}
}
HashSet<DownloadChapterTask> toRemove = new();
foreach (KeyValuePair<DownloadChapterTask,Task> removeTask in _runningDownloadChapterTasks)
{
if (removeTask.Key.GetType() == typeof(DownloadChapterTask) &&
DateTime.Now.Subtract(removeTask.Key.lastChange) > TimeSpan.FromMinutes(3))//3 Minutes since last update to task -> remove
{
logger?.WriteLine(this.GetType().ToString(), $"Disposing failed task {removeTask.Key}.");
removeTask.Value.Dispose();;
toRemove.Add(removeTask.Key);
}
}
foreach (DownloadChapterTask taskToRemove in toRemove)
{
DeleteTask(taskToRemove);
AddTask(new DownloadChapterTask(taskToRemove.task, taskToRemove.connectorName,
taskToRemove.publication, taskToRemove.chapter, taskToRemove.language,
taskToRemove.parentTask));
}
if(allTasksWaitingLength != _allTasks.Count(task => task.state is TrangaTask.ExecutionState.Waiting))
ExportDataAndSettings();
allTasksWaitingLength = _allTasks.Count(task => task.state is TrangaTask.ExecutionState.Waiting);
@ -135,6 +157,8 @@ public class TaskManager
{
task.Execute(this, this.logger);
});
if(task.GetType() == typeof(DownloadChapterTask))
_runningDownloadChapterTasks.Add((DownloadChapterTask)task, t);
t.Start();
}
@ -152,9 +176,9 @@ public class TaskManager
case TrangaTask.Task.DownloadNewChapters:
IEnumerable<TrangaTask> matchingdnc =
_allTasks.Where(mTask => mTask.GetType() == typeof(DownloadNewChaptersTask));
if (matchingdnc.All(mTask =>
((DownloadNewChaptersTask)mTask).publication.internalId != ((DownloadNewChaptersTask)newTask).publication.publicationId &&
((DownloadNewChaptersTask)mTask).connectorName != ((DownloadNewChaptersTask)newTask).connectorName))
if (!matchingdnc.Any(mTask =>
((DownloadNewChaptersTask)mTask).publication.internalId == ((DownloadNewChaptersTask)newTask).publication.internalId &&
((DownloadNewChaptersTask)mTask).connectorName == ((DownloadNewChaptersTask)newTask).connectorName))
_allTasks.Add(newTask);
else
logger?.WriteLine(this.GetType().ToString(), $"Task already exists {newTask}");
@ -178,9 +202,11 @@ public class TaskManager
{
logger?.WriteLine(this.GetType().ToString(), $"Removing Task {removeTask}");
_allTasks.Remove(removeTask);
if (removeTask.GetType() == typeof(DownloadChapterTask))
_runningDownloadChapterTasks.Remove((DownloadChapterTask)removeTask);
}
public TrangaTask? AddTask(TrangaTask.Task taskType, string? connectorName, string? publicationId,
public TrangaTask? AddTask(TrangaTask.Task taskType, string? connectorName, string? internalId,
TimeSpan reoccurrenceTime, string? language = "en")
{
TrangaTask? newTask = null;
@ -190,10 +216,16 @@ public class TaskManager
newTask = new UpdateLibrariesTask(taskType, reoccurrenceTime);
break;
case TrangaTask.Task.DownloadNewChapters:
if(connectorName is null || publicationId is null || language is null)
logger?.WriteLine(this.GetType().ToString(), $"Values connectorName, publicationName and language can not be null.");
if (connectorName is null)
logger?.WriteLine(this.GetType().ToString(), $"Value connectorName can not be null.");
if(internalId is null)
logger?.WriteLine(this.GetType().ToString(), $"Value internalId can not be null.");
if(language is null)
logger?.WriteLine(this.GetType().ToString(), $"Value language can not be null.");
if (connectorName is null || internalId is null || language is null)
return null;
GetConnector(connectorName); //Check if connectorName is valid
Publication publication = GetAllPublications().First(pub => pub.internalId == publicationId);
Publication publication = GetAllPublications().First(pub => pub.internalId == internalId);
newTask = new DownloadNewChaptersTask(taskType, connectorName!, publication, reoccurrenceTime, language!);
break;
}

View File

@ -23,7 +23,7 @@ public abstract class TrangaTask
[Newtonsoft.Json.JsonIgnore]public ExecutionState state { get; set; }
[Newtonsoft.Json.JsonIgnore]public float progress { get; protected set; }
[Newtonsoft.Json.JsonIgnore]public DateTime nextExecution => lastExecuted.Add(reoccurrence);
[Newtonsoft.Json.JsonIgnore]public DateTime executionStarted { get; protected set; }
[Newtonsoft.Json.JsonIgnore]public DateTime executionStarted { get; private set; }
[Newtonsoft.Json.JsonIgnore]
public DateTime executionApproximatelyFinished => this.progress != 0
@ -32,6 +32,8 @@ public abstract class TrangaTask
[Newtonsoft.Json.JsonIgnore]
public TimeSpan executionApproximatelyRemaining => this.executionApproximatelyFinished.Subtract(DateTime.Now);
[Newtonsoft.Json.JsonIgnore]public DateTime lastChange { get; protected set; }
public enum ExecutionState
{
@ -47,11 +49,13 @@ public abstract class TrangaTask
this.task = task;
this.progress = 0f;
this.executionStarted = DateTime.Now;
this.lastChange = DateTime.Now;
}
public float IncrementProgress(float amount)
{
this.progress += amount;
this.lastChange = DateTime.Now;
return this.progress;
}

View File

@ -9,7 +9,7 @@ public class DownloadChapterTask : TrangaTask
public Publication publication { get; }
public string language { get; }
public Chapter chapter { get; }
[JsonIgnore]private DownloadNewChaptersTask? parentTask { get; init; }
[JsonIgnore]public DownloadNewChaptersTask? parentTask { get; init; }
public DownloadChapterTask(Task task, string connectorName, Publication publication, Chapter chapter, string language = "en", DownloadNewChaptersTask? parentTask = null) : base(task, TimeSpan.Zero)
{
@ -22,15 +22,15 @@ public class DownloadChapterTask : TrangaTask
protected override void ExecuteTask(TaskManager taskManager, Logger? logger)
{
Publication pub = (Publication)this.publication!;
Connector connector = taskManager.GetConnector(this.connectorName);
connector.DownloadChapter(pub, this.chapter, this);
connector.DownloadChapter(this.publication, this.chapter, this);
taskManager.DeleteTask(this);
}
public new float IncrementProgress(float amount)
{
this.progress += amount;
this.lastChange = DateTime.Now;
parentTask?.IncrementProgress(amount);
return this.progress;
}

View File

@ -21,6 +21,7 @@ public class DownloadNewChaptersTask : TrangaTask
public new float IncrementProgress(float amount)
{
this.progress += amount / this.childTaskAmount;
this.lastChange = DateTime.Now;
return this.progress;
}

View File

@ -43,25 +43,37 @@ function DeleteData(uri){
}
async function GetAvailableControllers(){
var uri = apiUri + "/Tranga/GetAvailableControllers";
var uri = apiUri + "/Controllers/Get";
let json = await GetData(uri);
return json;
}
async function GetPublication(connectorName, title){
var uri = apiUri + `/Tranga/GetPublicationsFromConnector?connectorName=${connectorName}&title=${title}`;
async function GetPublicationFromConnector(connectorName, title){
var uri = apiUri + `/Publications/GetFromConnector?connectorName=${connectorName}&title=${title}`;
let json = await GetData(uri);
return json;
}
async function GetKnownPublications(){
var uri = apiUri + "/Tranga/GetKnownPublications";
var uri = apiUri + "/Publications/GetKnown";
let json = await GetData(uri);
return json;
}
async function GetPublication(internalId){
var uri = apiUri + `/Publications/GetKnown?internalId=${internalId}`;
let json = await GetData(uri);
return json;
}
async function GetChapters(internalId, connectorName, language){
var uri = apiUri + `/Publications/GetChapters?internalId=${internalId}&connectorName=${connectorName}&language=${language}`;
let json = await GetData(uri);
return json;
}
async function GetTaskTypes(){
var uri = apiUri + "/Tasks/GetTaskTypes";
var uri = apiUri + "/Tasks/GetTypes";
let json = await GetData(uri);
return json;
}
@ -89,8 +101,18 @@ async function GetKomgaTask(){
return json;
}
function CreateTask(taskType, reoccurrence, connectorName, publicationId, language){
var uri = apiUri + `/Tasks/Create?taskType=${taskType}&connectorName=${connectorName}&publicationId=${publicationId}&reoccurrenceTime=${reoccurrence}&language=${language}`;
function CreateMonitorTask(connectorName, internalId, reoccurrence, language){
var uri = apiUri + `/Tasks/CreateMonitorTask?connectorName=${connectorName}&internalId=${internalId}&reoccurrenceTime=${reoccurrence}&language=${language}`;
PostData(uri);
}
function CreateUpdateLibraryTask(reoccurrence){
var uri = apiUri + `/Tasks/CreateUpdateLibraryTask?reoccurrenceTime=${reoccurrence}`;
PostData(uri);
}
function CreateDownloadChaptersTask(connectorName, internalId, chapters, language){
var uri = apiUri + `/Tasks/CreateDownloadChaptersTask?connectorName=${connectorName}&internalId=${internalId}&chapters=${chapters}&language=${language}`;
PostData(uri);
}

View File

@ -25,36 +25,64 @@
<p>+</p>
</div>
<publication>
<img src="media/cover.jpg">
<img alt="cover" src="media/cover.jpg">
<publication-information>
<connector-name class="pill">MangaDex</connector-name>
<publication-name>Tensei Pandemic</publication-name>
</publication-information>
</publication>
</content>
<popup id="addTaskPopup">
<popup id="selectPublicationPopup">
<blur-background id="blurBackgroundTaskPopup"></blur-background>
<addtask-window>
<window-titlebar>
<p>Add Task</p>
<img id="closePopupImg" src="media/close-x.svg" alt="Close">
</window-titlebar>
<window-content>
<addtask-settings>
<addtask-setting><label for="selectReccurrence">Recurrence</label><input id="selectReccurrence" type="time" value="01:00:00" step="3600"></addtask-setting>
<addtask-setting><label for="connectors">Connector</label>
<select id="connectors">
<option value=""></option>
</select>
</addtask-setting>
<addtask-setting><label for="searchPublicationQuery">Search Title</label><input id="searchPublicationQuery" type="text"></addtask-setting>
<input type="submit" value="Search" onclick="NewSearch();">
</addtask-settings>
<div id="taskSelectOutput"></div>
</window-content>
</addtask-window>
<popup-window>
<popup-title>Select Publication</popup-title>
<popup-content>
<div>
<label for="connectors">Connector</label>
<select id="connectors">
<option value=""></option>
</select>
</div>
<div>
<label for="searchPublicationQuery">Search Title</label><input id="searchPublicationQuery" type="text"></addtask-setting>
</div>
<input type="submit" value="Search" style="font-weight: bolder" onclick="NewSearch();">
</popup-content>
<div id="taskSelectOutput"></div>
</popup-window>
</popup>
<popup id="createMonitorTaskPopup">
<blur-background id="blurBackgroundCreateMonitorTaskPopup"></blur-background>
<popup-window>
<popup-title>Create Task: Monitor Publication</popup-title>
<popup-content>
<div>
<p>Every</p>
<label for="hours">Hours: </label><input id="hours" type="number" value="3" min="0" max="23">
<label for="minutes">Minutes: </label><input id="minutes" type="number" value="0" min="0" max="59">
<input type="submit" value="Create" onclick="AddMonitorTask()">
</div>
</popup-content>
</popup-window>
</popup>
<popup id="createDownloadChaptersTask">
<blur-background id="blurBackgroundCreateDownloadChaptersTask"></blur-background>
<popup-window>
<popup-title>Create Task: Download Chapter(s)</popup-title>
<popup-content>
<div>
<label for="selectedChapters">Chapters:</label><input id="selectedChapters" placeholder="Select"><input type="submit" value="Select" onclick="DownloadChapterTaskClick()">
</div>
<div id="chapterOutput">
</div>
</popup-content>
</popup-window>
</popup>
<popup id="publicationViewerPopup">
<blur-background id="blurBackgroundPublicationPopup"></blur-background>
<publication-viewer>
@ -71,42 +99,46 @@
<publication-interactions>
<publication-starttask>Start Task ▶️</publication-starttask>
<publication-delete>Delete Task ❌</publication-delete>
<publication-add>Add Task </publication-add>
<publication-add id="createMonitorTaskButton">Monitor </publication-add>
<publication-add id="createDownloadChapterTaskButton">Download Chapter </publication-add>
</publication-interactions>
</publication-information>
</publication-viewer>
</popup>
<popup id="settingsPopup">
<blur-background id="blurBackgroundSettingsPopup"></blur-background>
<settings>
<span style="font-weight: bold; text-align: center; font-size: 16pt;">Settings</span>
<div>
<p class="title">Download Location:</p>
<span id="downloadLocation"></span>
</div>
<div>
<p class="title">API-URI</p>
<label for="settingApiUri"></label><input placeholder="https://" type="text" id="settingApiUri">
</div>
<komga-settings>
<span class="title">Komga</span>
<div>Configured: <span id="komgaConfigured">✅❌</span></div>
<label for="komgaUrl"></label><input placeholder="URL" id="komgaUrl" type="text">
<label for="komgaUsername"></label><input placeholder="Username" id="komgaUsername" type="text">
<label for="komgaPassword"></label><input placeholder="Password" id="komgaPassword" type="password">
</komga-settings>
<kavita-settings>
<span class="title">Kavita</span>
<div>Configured: <span id="kavitaConfigured">✅❌</span></div>
<label for="kavitaUrl"></label><input placeholder="URL" id="kavitaUrl" type="text">
<label for="kavitaUsername"></label><input placeholder="Username" id="kavitaUsername" type="text">
<label for="kavitaPassword"></label><input placeholder="Password" id="kavitaPassword" type="password">
</kavita-settings>
<div>
<label for="libraryUpdateTime" style="margin-right: 5px;">Update Time</label><input id="libraryUpdateTime" type="time" value="00:01:00" step="10">
<input type="submit" value="Update" onclick="UpdateLibrarySettings()">
</div>
</settings>
<popup-window>
<popup-title>Settings</popup-title>
<popup-content>
<div>
<p class="title">Download Location:</p>
<span id="downloadLocation"></span>
</div>
<div>
<p class="title">API-URI</p>
<label for="settingApiUri"></label><input placeholder="https://" type="text" id="settingApiUri">
</div>
<div>
<span class="title">Komga</span>
<div>Configured: <span id="komgaConfigured">✅❌</span></div>
<label for="komgaUrl"></label><input placeholder="URL" id="komgaUrl" type="text">
<label for="komgaUsername"></label><input placeholder="Username" id="komgaUsername" type="text">
<label for="komgaPassword"></label><input placeholder="Password" id="komgaPassword" type="password">
</div>
<div>
<span class="title">Kavita</span>
<div>Configured: <span id="kavitaConfigured">✅❌</span></div>
<label for="kavitaUrl"></label><input placeholder="URL" id="kavitaUrl" type="text">
<label for="kavitaUsername"></label><input placeholder="Username" id="kavitaUsername" type="text">
<label for="kavitaPassword"></label><input placeholder="Password" id="kavitaPassword" type="password">
</div>
<div>
<label for="libraryUpdateTime" style="margin-right: 5px;">Update Time</label><input id="libraryUpdateTime" type="time" value="00:01:00" step="10">
<input type="submit" value="Update" onclick="UpdateLibrarySettings()">
</div>
</popup-content>
</popup-window>
</popup>
</viewport>
<footer>

View File

@ -10,7 +10,13 @@ const settingsPopup = document.querySelector("#settingsPopup");
const settingsCog = document.querySelector("#settingscog");
const selectRecurrence = document.querySelector("#selectReccurrence");
const tasksContent = document.querySelector("content");
const addTaskPopup = document.querySelector("#addTaskPopup");
const selectPublicationPopup = document.querySelector("#selectPublicationPopup");
const createMonitorTaskButton = document.querySelector("#createMonitorTaskButton");
const createDownloadChapterTaskButton = document.querySelector("#createDownloadChapterTaskButton");
const createMonitorTaskPopup = document.querySelector("#createMonitorTaskPopup");
const createDownloadChaptersTask = document.querySelector("#createDownloadChaptersTask");
const chapterOutput = document.querySelector("#chapterOutput");
const selectedChapters = document.querySelector("#selectedChapters");
const publicationViewerPopup = document.querySelector("#publicationViewerPopup");
const publicationViewerWindow = document.querySelector("publication-viewer");
const publicationViewerDescription = document.querySelector("#publicationViewerDescription");
@ -19,9 +25,7 @@ const publicationViewerTags = document.querySelector("#publicationViewerTags");
const publicationViewerAuthor = document.querySelector("#publicationViewerAuthor");
const pubviewcover = document.querySelector("#pubviewcover");
const publicationDelete = document.querySelector("publication-delete");
const publicationAdd = document.querySelector("publication-add");
const publicationTaskStart = document.querySelector("publication-starttask");
const closetaskpopup = document.querySelector("#closePopupImg");
const settingDownloadLocation = document.querySelector("#downloadLocation");
const settingKomgaUrl = document.querySelector("#komgaUrl");
const settingKomgaUser = document.querySelector("#komgaUsername");
@ -41,12 +45,25 @@ const tagTasksPopupContent = document.querySelector("footer-tag-content");
searchbox.addEventListener("keyup", (event) => FilterResults());
settingsCog.addEventListener("click", () => OpenSettings());
document.querySelector("#blurBackgroundSettingsPopup").addEventListener("click", () => HideSettings());
closetaskpopup.addEventListener("click", () => HideAddTaskPopup());
document.querySelector("#blurBackgroundTaskPopup").addEventListener("click", () => HideAddTaskPopup());
document.querySelector("#blurBackgroundSettingsPopup").addEventListener("click", () => settingsPopup.style.display = "none");
document.querySelector("#blurBackgroundTaskPopup").addEventListener("click", () => selectPublicationPopup.style.display = "none");
document.querySelector("#blurBackgroundPublicationPopup").addEventListener("click", () => HidePublicationPopup());
document.querySelector("#blurBackgroundCreateMonitorTaskPopup").addEventListener("click", () => createMonitorTaskPopup.style.display = "none");
document.querySelector("#blurBackgroundCreateDownloadChaptersTask").addEventListener("click", () => createDownloadChaptersTask.style.display = "none");
selectedChapters.addEventListener("keypress", (event) => {
if(event.key === "Enter"){
DownloadChapterTaskClick();
}
})
publicationDelete.addEventListener("click", () => DeleteTaskClick());
publicationAdd.addEventListener("click", () => AddTaskClick());
createMonitorTaskButton.addEventListener("click", () => {
HidePublicationPopup();
createMonitorTaskPopup.style.display = "block";
});
createDownloadChapterTaskButton.addEventListener("click", () => {
HidePublicationPopup();
OpenDownloadChapterTaskPopup();
})
publicationTaskStart.addEventListener("click", () => StartTaskClick());
settingApiUri.addEventListener("keypress", (event) => {
if(event.key === "Enter"){
@ -82,18 +99,14 @@ GetAvailableControllers()
function NewSearch(){
//Disable inputs
selectRecurrence.disabled = true;
connectorSelect.disabled = true;
searchPublicationQuery.disabled = true;
//Waitcursor
document.body.style.cursor = "wait";
selectRecurrence.style.cursor = "wait";
connectorSelect.style.cursor = "wait";
searchPublicationQuery.style.cursor = "wait";
//Empty previous results
selectPublication.replaceChildren();
GetPublication(connectorSelect.value, searchPublicationQuery.value)
GetPublicationFromConnector(connectorSelect.value, searchPublicationQuery.value)
.then(json =>
json.forEach(publication => {
var option = CreatePublication(publication, connectorSelect.value);
@ -105,14 +118,10 @@ function NewSearch(){
))
.then(() => {
//Re-enable inputs
selectRecurrence.disabled = false;
connectorSelect.disabled = false;
searchPublicationQuery.disabled = false;
//Cursor
document.body.style.cursor = "initial";
selectRecurrence.style.cursor = "initial";
connectorSelect.style.cursor = "initial";
searchPublicationQuery.style.cursor = "initial";
});
}
@ -137,18 +146,57 @@ function CreatePublication(publication, connector){
return publicationElement;
}
function AddMonitorTask(){
var hours = document.querySelector("#hours").value;
var minutes = document.querySelector("#minutes").value;
CreateMonitorTask(connectorSelect.value, toEditId, `${hours}:${minutes}:00`, "en");
HidePublicationPopup();
createMonitorTaskPopup.style.display = "none";
selectPublicationPopup.style.display = "none";
}
function OpenDownloadChapterTaskPopup(){
createDownloadChaptersTask.style.display = "block";
GetChapters(toEditId, connectorSelect.value, "en").then((json) => {
var i = 0;
json.forEach(chapter => {
var chapterDom = document.createElement("div");
var indexDom = document.createElement("span");
indexDom.className = "index";
indexDom.innerText = i++;
chapterDom.appendChild(indexDom);
var volDom = document.createElement("span");
volDom.className = "vol";
volDom.innerText = chapter.volumeNumber;
chapterDom.appendChild(volDom);
var chDom = document.createElement("span");
chDom.className = "ch";
chDom.innerText = chapter.chapterNumber;
chapterDom.appendChild(chDom);
var titleDom = document.createElement("span");
titleDom.innerText = chapter.name;
chapterDom.appendChild(titleDom);
chapterOutput.appendChild(chapterDom);
});
});
}
function DownloadChapterTaskClick(){
CreateDownloadChaptersTask(connectorSelect.value, toEditId, selectedChapters.value, "en");
HidePublicationPopup();
createDownloadChaptersTask.style.display = "none";
selectPublicationPopup.style.display = "none";
}
function DeleteTaskClick(){
taskToDelete = tasks.filter(tTask => tTask.publication.internalId === toEditId)[0];
DeleteTask("DownloadNewChapters", taskToDelete.connectorName, toEditId);
HidePublicationPopup();
}
function AddTaskClick(){
CreateTask("DownloadNewChapters", selectRecurrence.value, connectorSelect.value, toEditId, "en")
HideAddTaskPopup();
HidePublicationPopup();
}
function StartTaskClick(){
var toEditTask = tasks.filter(task => task.publication.internalId == toEditId)[0];
StartTask("DownloadNewChapters", toEditTask.connectorName, toEditId);
@ -194,12 +242,14 @@ function ShowPublicationViewerWindow(publicationId, event, add){
//Check what action should be listed
if(add){
publicationAdd.style.display = "initial";
createMonitorTaskButton.style.display = "initial";
createDownloadChapterTaskButton.style.display = "initial";
publicationDelete.style.display = "none";
publicationTaskStart.style.display = "none";
}
else{
publicationAdd.style.display = "none";
createMonitorTaskButton.style.display = "none";
createDownloadChapterTaskButton.style.display = "none";
publicationDelete.style.display = "initial";
publicationTaskStart.style.display = "initial";
}
@ -211,10 +261,7 @@ function HidePublicationPopup(){
function ShowNewTaskWindow(){
selectPublication.replaceChildren();
addTaskPopup.style.display = "block";
}
function HideAddTaskPopup(){
addTaskPopup.style.display = "none";
selectPublicationPopup.style.display = "flex";
}
@ -234,9 +281,6 @@ function OpenSettings(){
settingsPopup.style.display = "flex";
}
function HideSettings(){
settingsPopup.style.display = "none";
}
function GetSettingsClick(){
settingApiUri.value = "";
@ -288,7 +332,7 @@ function UpdateLibrarySettings(){
if(settingKavitaUrl.value != "" && settingKavitaUser.value != "" && settingKavitaPass.value != ""){
UpdateSettings("", "", "", settingKavitaUrl.value, settingKavitaUser.value, settingKavitaPass.value);
}
CreateTask("UpdateLibraries", libraryUpdateTime.value, "","","");
CreateUpdateLibraryTask(libraryUpdateTime.value);
setTimeout(() => GetSettingsClick(), 200);
}

View File

@ -147,46 +147,22 @@ content {
align-content: start;
}
settings {
width: 50%;
background-color: var(--accent-color);
display: flex;
flex-direction: column;
z-index: 10;
position: absolute;
left: 25%;
top: 100px;
border-radius: 5px;
padding: 10px 0;
}
#settingsPopup{
z-index: 10;
}
settings > * {
margin: 0 20%;
}
settings input {
margin: 3px 0;
padding: 3px;
border-radius: 3px;
border: 1px solid rgba(0,0,0,0.2);
width: 100%;
}
settings .title {
font-weight: bolder;
font-size: 14pt;
margin: 15px 0 2px 0;
}
komga-settings {
margin-top: 20px;
display: flex;
#settingsPopup popup-content{
flex-direction: column;
flex-wrap: nowrap;
align-items: start;
margin: 15px 10px;
}
#settingsPopup popup-content > * {
margin: 5px 10px;
}
#settingsPopup popup-content .title {
font-weight: bolder;
}
#addPublication {
@ -281,6 +257,112 @@ popup{
left: 0;
position: fixed;
z-index: 2;
flex-direction: column;
}
popup popup-window {
position: absolute;
z-index: 3;
left: 25%;
top: 100px;
width: 50%;
display: flex;
flex-direction: column;
background-color: var(--second-background-color);
border-radius: 3px;
overflow: hidden;
}
popup popup-window popup-title {
height: 30px;
font-size: 14pt;
font-weight: bolder;
padding: 5px 10px;
margin: 0;
display: flex;
align-items: center;
background-color: var(--primary-color);
color: var(--accent-color)
}
popup popup-window popup-content{
margin: 15px 10px;
display: flex;
align-items: center;
justify-content: space-evenly;
}
popup popup-window popup-content div > * {
margin: 2px 3px 0 0;
}
popup popup-window popup-content input, select {
padding: 3px 4px;
width: 130px;
border: 1px solid lightgrey;
background-color: var(--accent-color);
border-radius: 3px;
}
#selectPublicationPopup publication {
width: 150px;
height: 250px;
}
#createTaskPopup {
z-index: 7;
}
#createTaskPopup input {
height: 30px;
width: 200px;
}
#createMonitorTaskPopup, #createDownloadChaptersTask {
z-index: 9;
}
#createDownloadChaptersTask popup-content {
flex-direction: column;
align-items: start;
}
#createDownloadChaptersTask popup-content > * {
margin: 3px 0;
}
#createDownloadChaptersTask #chapterOutput {
max-height: 50vh;
overflow-y: scroll;
}
#createDownloadChaptersTask #chapterOutput .index{
display: inline-block;
width: 25px;
}
#createDownloadChaptersTask #chapterOutput .index::after{
content: ':';
}
#createDownloadChaptersTask #chapterOutput .vol::before{
content: 'Vol.';
}
#createDownloadChaptersTask #chapterOutput .vol{
display: inline-block;
width: 45px;
}
#createDownloadChaptersTask #chapterOutput .ch::before{
content: 'Ch.';
}
#createDownloadChaptersTask #chapterOutput .ch {
display: inline-block;
width: 60px;
}
blur-background {
@ -292,79 +374,6 @@ blur-background {
opacity: 0.5;
}
addtask-window {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
position: absolute;
left: 12.5%;
top: 15%;
width: 75%;
min-height: 70%;
max-height: 80%;
padding: 0;
background-color: var(--accent-color);
border-radius: 5px;
}
window-titlebar {
width: 100%;
height: 60px;
background-color: var(--primary-color);
border-radius: 5px 5px 0 0;
color: var(--accent-color);
display: flex block;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
window-titlebar p {
margin: 0 30px;
font-size: 14pt;
font-weight: bolder;
letter-spacing: 1px;
}
window-titlebar #closePopupImg {
height: 70%;
cursor: pointer;
margin-right: 20px;
filter: invert(100%) sepia(0%) saturate(100%) hue-rotate(115deg) brightness(116%) contrast(101%);
}
window-content {
display: flex;
flex-direction: column;
padding: 20px 5%;
overflow-x: scroll;
}
addtask-settings{
display: flex;
justify-content: center;
align-items: center;
}
addtask-settings select, addtask-settings input{
padding: 5px;
font-size: 10pt;
border: 1px solid rgba(0,0,0,0.2);
border-radius: 3px;
background-color: transparent;
margin: 10px 0;
width: 150px;
}
addtask-settings label {
font-weight: bolder;
margin: 0 5px;
}
addtask-settings addtask-setting{
margin: 0 15px;
}
#taskSelectOutput{
display: flex;
flex-direction: row;
@ -512,7 +521,7 @@ footer-tag-popup::before{
border-left: 10px solid transparent;
border-top: 10px solid var(--second-background-color);
border-bottom: 10px solid transparent;
left: 0px;
left: 0;
bottom: -17px;
border-radius: 0 0 0 5px;
}