Compare commits

..

No commits in common. "3b542c04f647dad096e622028db02f8295cb9006" and "23dfdc093324816a9517670515e3c832e4751a9f" have entirely different histories.

11 changed files with 275 additions and 211 deletions

View File

@ -112,12 +112,14 @@ app.MapPost("/Tasks/CreateMonitorTask",
Publication? publication = taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == internalId); Publication? publication = taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == internalId);
if (publication is null) if (publication is null)
return; return;
taskManager.AddTask(new MonitorPublicationTask(connectorName, (Publication)publication, TimeSpan.Parse(reoccurrenceTime), language ?? "en")); taskManager.AddTask(new DownloadNewChaptersTask(TrangaTask.Task.DownloadNewChapters, connectorName,
(Publication)publication,
TimeSpan.Parse(reoccurrenceTime), language ?? "en"));
}); });
app.MapPost("/Tasks/CreateUpdateLibraryTask", (string reoccurrenceTime) => app.MapPost("/Tasks/CreateUpdateLibraryTask", (string reoccurrenceTime) =>
{ {
taskManager.AddTask(new UpdateLibrariesTask(TimeSpan.Parse(reoccurrenceTime))); taskManager.AddTask(new UpdateLibrariesTask(TrangaTask.Task.UpdateLibraries, TimeSpan.Parse(reoccurrenceTime)));
}); });
app.MapPost("/Tasks/CreateDownloadChaptersTask", (string connectorName, string internalId, string chapters, string? language) => { app.MapPost("/Tasks/CreateDownloadChaptersTask", (string connectorName, string internalId, string chapters, string? language) => {
@ -132,14 +134,14 @@ app.MapPost("/Tasks/CreateDownloadChaptersTask", (string connectorName, string i
IEnumerable<Chapter> toDownload = connector.SearchChapters((Publication)publication, chapters, language ?? "en"); IEnumerable<Chapter> toDownload = connector.SearchChapters((Publication)publication, chapters, language ?? "en");
foreach(Chapter chapter in toDownload) foreach(Chapter chapter in toDownload)
taskManager.AddTask(new DownloadChapterTask(connectorName, (Publication)publication, chapter, "en")); taskManager.AddTask(new DownloadChapterTask(TrangaTask.Task.DownloadChapter, connectorName,
(Publication)publication, chapter, "en"));
}); });
app.MapDelete("/Tasks/Delete", (string taskType, string? connectorName, string? publicationId) => app.MapDelete("/Tasks/Delete", (string taskType, string? connectorName, string? publicationId) =>
{ {
TrangaTask.Task task = Enum.Parse<TrangaTask.Task>(taskType); TrangaTask.Task task = Enum.Parse<TrangaTask.Task>(taskType);
foreach(TrangaTask tTask in taskManager.GetTasksMatching(task, connectorName, internalId: publicationId)) taskManager.DeleteTask(task, connectorName, publicationId);
taskManager.DeleteTask(tTask);
}); });
app.MapGet("/Tasks/Get", (string taskType, string? connectorName, string? searchString) => app.MapGet("/Tasks/Get", (string taskType, string? connectorName, string? searchString) =>
@ -165,7 +167,7 @@ app.MapGet("/Tasks/GetProgress", (string taskType, string connectorName, string
{ {
TrangaTask? task = null; TrangaTask? task = null;
TrangaTask.Task pTask = Enum.Parse<TrangaTask.Task>(taskType); TrangaTask.Task pTask = Enum.Parse<TrangaTask.Task>(taskType);
if (pTask is TrangaTask.Task.MonitorPublication) if (pTask is TrangaTask.Task.DownloadNewChapters)
{ {
task = taskManager.GetTasksMatching(pTask, connectorName: connectorName, internalId: publicationId).FirstOrDefault(); task = taskManager.GetTasksMatching(pTask, connectorName: connectorName, internalId: publicationId).FirstOrDefault();
}else if (pTask is TrangaTask.Task.DownloadChapter && chapterSortNumber is not null) }else if (pTask is TrangaTask.Task.DownloadChapter && chapterSortNumber is not null)
@ -190,7 +192,7 @@ app.MapPost("/Tasks/Start", (string taskType, string? connectorName, string? int
{ {
TrangaTask.Task pTask = Enum.Parse<TrangaTask.Task>(taskType); TrangaTask.Task pTask = Enum.Parse<TrangaTask.Task>(taskType);
TrangaTask? task = taskManager TrangaTask? task = taskManager
.GetTasksMatching(pTask, connectorName: connectorName, internalId: internalId).FirstOrDefault(); .GetTasksMatching(pTask, connectorName: connectorName, internalId: internalId)?.FirstOrDefault();
if (task is null) if (task is null)
return; return;
@ -215,7 +217,7 @@ app.MapPost("/Queue/Enqueue", (string taskType, string? connectorName, string? p
{ {
TrangaTask.Task pTask = Enum.Parse<TrangaTask.Task>(taskType); TrangaTask.Task pTask = Enum.Parse<TrangaTask.Task>(taskType);
TrangaTask? task = taskManager TrangaTask? task = taskManager
.GetTasksMatching(pTask, connectorName: connectorName, internalId: publicationId).FirstOrDefault(); .GetTasksMatching(pTask, connectorName: connectorName, internalId: publicationId)?.First();
if (task is null) if (task is null)
return; return;
@ -233,7 +235,7 @@ app.MapDelete("/Queue/Dequeue", (string taskType, string? connectorName, string?
{ {
TrangaTask.Task pTask = Enum.Parse<TrangaTask.Task>(taskType); TrangaTask.Task pTask = Enum.Parse<TrangaTask.Task>(taskType);
TrangaTask? task = taskManager TrangaTask? task = taskManager
.GetTasksMatching(pTask, connectorName: connectorName, internalId: publicationId).FirstOrDefault(); .GetTasksMatching(pTask, connectorName: connectorName, internalId: publicationId)?.First();
if (task is null) if (task is null)
return; return;

View File

@ -335,9 +335,8 @@ public static class Tranga_Cli
TimeSpan reoccurrence = SelectReoccurrence(logger); TimeSpan reoccurrence = SelectReoccurrence(logger);
logger.WriteLine("Tranga_CLI", "Sending Task to TaskManager"); logger.WriteLine("Tranga_CLI", "Sending Task to TaskManager");
TrangaTask nTask = new MonitorPublicationTask(connector.name, (Publication)publication, reoccurrence, "en"); TrangaTask? newTask = taskManager.AddTask(TrangaTask.Task.DownloadNewChapters, connector.name, publication.Value.publicationId, reoccurrence, "en");
taskManager.AddTask(nTask); Console.WriteLine(newTask);
Console.WriteLine(nTask);
} }
private static void AddTaskToQueue(TaskManager taskManager, Logger logger) private static void AddTaskToQueue(TaskManager taskManager, Logger logger)
@ -409,19 +408,20 @@ public static class Tranga_Cli
return; return;
} }
if (task is TrangaTask.Task.MonitorPublication) if (task is TrangaTask.Task.DownloadNewChapters)
{ {
TimeSpan reoccurrence = SelectReoccurrence(logger); TimeSpan reoccurrence = SelectReoccurrence(logger);
logger.WriteLine("Tranga_CLI", "Sending Task to TaskManager"); logger.WriteLine("Tranga_CLI", "Sending Task to TaskManager");
TrangaTask newTask = new MonitorPublicationTask(connector!.name, (Publication)publication!, reoccurrence, "en"); TrangaTask newTask = new DownloadNewChaptersTask(TrangaTask.Task.DownloadNewChapters, connector!.name, (Publication)publication!, reoccurrence, "en");
taskManager.AddTask(newTask); taskManager.AddTask(newTask);
Console.WriteLine(newTask); Console.WriteLine(newTask);
}else if (task is TrangaTask.Task.DownloadChapter) }else if (task is TrangaTask.Task.DownloadChapter)
{ {
foreach (Chapter chapter in SelectChapters(connector!, (Publication)publication!, logger)) foreach (Chapter chapter in SelectChapters(connector!, (Publication)publication!, logger))
{ {
TrangaTask newTask = new DownloadChapterTask(connector!.name, (Publication)publication, chapter, "en"); TrangaTask newTask = new DownloadChapterTask(TrangaTask.Task.DownloadChapter, connector!.name,
(Publication)publication!, chapter, "en");
taskManager.AddTask(newTask); taskManager.AddTask(newTask);
Console.WriteLine(newTask); Console.WriteLine(newTask);
} }

View File

@ -1,4 +1,5 @@
using System.IO.Compression; using System.IO.Compression;
using System.Net;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Xml.Linq; using System.Xml.Linq;
@ -19,7 +20,7 @@ public abstract class Connector
protected readonly Logger? logger; protected readonly Logger? logger;
private readonly string _imageCachePath; protected readonly string imageCachePath;
protected Connector(string downloadLocation, string imageCachePath, Logger? logger) protected Connector(string downloadLocation, string imageCachePath, Logger? logger)
{ {
@ -29,9 +30,9 @@ public abstract class Connector
{ {
//RequestTypes for RateLimits //RequestTypes for RateLimits
}, logger); }, logger);
this._imageCachePath = imageCachePath; this.imageCachePath = imageCachePath;
if (!Directory.Exists(imageCachePath)) if (!Directory.Exists(imageCachePath))
Directory.CreateDirectory(this._imageCachePath); Directory.CreateDirectory(this.imageCachePath);
} }
public abstract string name { get; } //Name of the Connector (e.g. Website) public abstract string name { get; } //Name of the Connector (e.g. Website)
@ -232,7 +233,6 @@ public abstract class Connector
/// <param name="comicInfoPath">Path of the generate Chapter ComicInfo.xml, if it was generated</param> /// <param name="comicInfoPath">Path of the generate Chapter ComicInfo.xml, if it was generated</param>
/// <param name="requestType">RequestType for RateLimits</param> /// <param name="requestType">RequestType for RateLimits</param>
/// <param name="referrer">Used in http request header</param> /// <param name="referrer">Used in http request header</param>
/// <param name="cancellationToken"></param>
protected bool DownloadChapterImages(string[] imageUrls, string saveArchiveFilePath, byte requestType, DownloadChapterTask parentTask, string? comicInfoPath = null, string? referrer = null, CancellationToken? cancellationToken = null) protected bool DownloadChapterImages(string[] imageUrls, string saveArchiveFilePath, byte requestType, DownloadChapterTask parentTask, string? comicInfoPath = null, string? referrer = null, CancellationToken? cancellationToken = null)
{ {
if (cancellationToken?.IsCancellationRequested??false) if (cancellationToken?.IsCancellationRequested??false)
@ -279,7 +279,7 @@ public abstract class Connector
{ {
string[] split = url.Split('/'); string[] split = url.Split('/');
string filename = split[^1]; string filename = split[^1];
string saveImagePath = Path.Join(_imageCachePath, filename); string saveImagePath = Path.Join(imageCachePath, filename);
if (File.Exists(saveImagePath)) if (File.Exists(saveImagePath))
return filename; return filename;
@ -294,15 +294,11 @@ public abstract class Connector
protected class DownloadClient protected class DownloadClient
{ {
private static readonly HttpClient Client = new() private static readonly HttpClient Client = new();
{
Timeout = TimeSpan.FromSeconds(60)
};
private readonly Dictionary<byte, DateTime> _lastExecutedRateLimit; private readonly Dictionary<byte, DateTime> _lastExecutedRateLimit;
private readonly Dictionary<byte, TimeSpan> _rateLimit; private readonly Dictionary<byte, TimeSpan> _rateLimit;
// ReSharper disable once InconsistentNaming private Logger? logger;
private readonly Logger? logger;
/// <summary> /// <summary>
/// Creates a httpClient /// Creates a httpClient

View File

@ -23,7 +23,6 @@ public abstract class LibraryManager
/// <param name="baseUrl">Base-URL of Komga instance, no trailing slashes(/)</param> /// <param name="baseUrl">Base-URL of Komga instance, no trailing slashes(/)</param>
/// <param name="auth">Base64 string of username and password (username):(password)</param> /// <param name="auth">Base64 string of username and password (username):(password)</param>
/// <param name="logger"></param> /// <param name="logger"></param>
/// <param name="libraryType"></param>
protected LibraryManager(string baseUrl, string auth, Logger? logger, LibraryType libraryType) protected LibraryManager(string baseUrl, string auth, Logger? logger, LibraryType libraryType)
{ {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;

View File

@ -7,8 +7,8 @@ namespace Tranga;
public abstract class NotificationManager public abstract class NotificationManager
{ {
protected readonly Logger? logger; protected Logger? logger;
public NotificationManagerType notificationManagerType; public NotificationManagerType notificationManagerType { get; }
protected NotificationManager(NotificationManagerType notificationManagerType, Logger? logger = null) protected NotificationManager(NotificationManagerType notificationManagerType, Logger? logger = null)
{ {

View File

@ -20,6 +20,17 @@ public class TaskManager
private readonly Dictionary<DownloadChapterTask, CancellationTokenSource> _runningDownloadChapterTasks = new(); private readonly Dictionary<DownloadChapterTask, CancellationTokenSource> _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>
/// <param name="libraryManagers"></param>
/// <param name="notificationManagers"></param>
/// <param name="logger"></param>
public TaskManager(string downloadFolderPath, string workingDirectory, string imageCachePath, HashSet<LibraryManager> libraryManagers, HashSet<NotificationManager> notificationManagers, Logger? logger = null) : this(new TrangaSettings(downloadFolderPath, workingDirectory, libraryManagers, notificationManagers), logger)
{
}
public TaskManager(TrangaSettings settings, Logger? logger = null) public TaskManager(TrangaSettings settings, Logger? logger = null)
{ {
this.logger = logger; this.logger = logger;
@ -54,47 +65,54 @@ public class TaskManager
waitingButExecute.state = TrangaTask.ExecutionState.Enqueued; waitingButExecute.state = TrangaTask.ExecutionState.Enqueued;
} }
foreach (TrangaTask enqueuedTask in _allTasks.Where(enqueuedTask => enqueuedTask.state is TrangaTask.ExecutionState.Enqueued)) foreach (TrangaTask task in _allTasks.Where(enqueuedTask => enqueuedTask.state is TrangaTask.ExecutionState.Enqueued))
{ {
switch (enqueuedTask.task) switch (task.task)
{ {
case TrangaTask.Task.DownloadChapter: case TrangaTask.Task.DownloadNewChapters:
case TrangaTask.Task.MonitorPublication:
if (!_allTasks.Any(taskQuery => if (!_allTasks.Any(taskQuery =>
taskQuery.task == TrangaTask.Task.DownloadNewChapters &&
taskQuery.state is TrangaTask.ExecutionState.Running &&
((DownloadNewChaptersTask)taskQuery).connectorName == ((DownloadNewChaptersTask)task).connectorName))
{ {
if (taskQuery.state is not TrangaTask.ExecutionState.Running) return false; ExecuteTaskNow(task);
switch (taskQuery)
{
case DownloadChapterTask dct when enqueuedTask is DownloadChapterTask eDct && dct.connectorName == eDct.connectorName:
case MonitorPublicationTask mpt when enqueuedTask is MonitorPublicationTask eMpt && mpt.connectorName == eMpt.connectorName:
return true;
default:
return false;
} }
})) break;
case TrangaTask.Task.DownloadChapter:
if (!_allTasks.Any(taskQuery =>
taskQuery.task == TrangaTask.Task.DownloadChapter &&
taskQuery.state is TrangaTask.ExecutionState.Running &&
((DownloadChapterTask)taskQuery).connectorName == ((DownloadChapterTask)task).connectorName))
{ {
ExecuteTaskNow(enqueuedTask); ExecuteTaskNow(task);
} }
break; break;
case TrangaTask.Task.UpdateLibraries: case TrangaTask.Task.UpdateLibraries:
ExecuteTaskNow(enqueuedTask); ExecuteTaskNow(task);
break; break;
} }
} }
foreach (TrangaTask failedTask in _allTasks.Where(taskQuery => HashSet<DownloadChapterTask> failedToRemove = new();
taskQuery.state is TrangaTask.ExecutionState.Failed)) foreach (KeyValuePair<DownloadChapterTask, CancellationTokenSource> removeTask in _runningDownloadChapterTasks)
{ {
switch (failedTask.task) if (removeTask.Key.GetType() == typeof(DownloadChapterTask) &&
DateTime.Now.Subtract(removeTask.Key.lastChange) > TimeSpan.FromMinutes(3))//3 Minutes since last update to task -> remove
{ {
case TrangaTask.Task.DownloadChapter: logger?.WriteLine(this.GetType().ToString(), $"Disposing failed task {removeTask.Key}.");
DeleteTask(failedTask); removeTask.Value.Cancel();
TrangaTask newTask = failedTask.Clone(); failedToRemove.Add(removeTask.Key);
failedTask.parentTask?.AddChildTask(newTask);
AddTask(newTask);
break;
} }
} }
foreach (DownloadChapterTask taskToRestart in failedToRemove)
{
DeleteTask(taskToRestart);
DownloadChapterTask newTask = new (taskToRestart.task, taskToRestart.connectorName,
taskToRestart.publication, taskToRestart.chapter, taskToRestart.language,
(DownloadNewChaptersTask?)taskToRestart.parentTask);
AddTask(newTask);
taskToRestart.parentTask?.ReplaceFailedChildTask(taskToRestart, newTask);
}
if(waitingTasksCount != _allTasks.Count(task => task.state is TrangaTask.ExecutionState.Waiting)) if(waitingTasksCount != _allTasks.Count(task => task.state is TrangaTask.ExecutionState.Waiting))
ExportDataAndSettings(); ExportDataAndSettings();
@ -132,19 +150,23 @@ public class TaskManager
_allTasks.RemoveWhere(trangaTask => trangaTask.task is TrangaTask.Task.UpdateLibraries); _allTasks.RemoveWhere(trangaTask => trangaTask.task is TrangaTask.Task.UpdateLibraries);
_allTasks.Add(newTask); _allTasks.Add(newTask);
break; break;
case TrangaTask.Task.MonitorPublication: case TrangaTask.Task.DownloadNewChapters:
if (!_allTasks.Any(mTask => mTask is MonitorPublicationTask mpt && newTask is MonitorPublicationTask nMpt && IEnumerable<TrangaTask> matchingdnc =
mpt.publication.internalId == nMpt.publication.internalId && _allTasks.Where(mTask => mTask.GetType() == typeof(DownloadNewChaptersTask));
mpt.connectorName == nMpt.connectorName)) if (!matchingdnc.Any(mTask =>
((DownloadNewChaptersTask)mTask).publication.internalId == ((DownloadNewChaptersTask)newTask).publication.internalId &&
((DownloadNewChaptersTask)mTask).connectorName == ((DownloadNewChaptersTask)newTask).connectorName))
_allTasks.Add(newTask); _allTasks.Add(newTask);
else else
logger?.WriteLine(this.GetType().ToString(), $"Task already exists {newTask}"); logger?.WriteLine(this.GetType().ToString(), $"Task already exists {newTask}");
break; break;
case TrangaTask.Task.DownloadChapter: case TrangaTask.Task.DownloadChapter:
if (!_allTasks.Any(mTask => mTask is DownloadChapterTask dct && newTask is DownloadChapterTask nDct && IEnumerable<TrangaTask> matchingdc =
dct.publication.internalId == nDct.publication.internalId && _allTasks.Where(mTask => mTask.GetType() == typeof(DownloadChapterTask));
dct.connectorName == nDct.connectorName && if (!matchingdc.Any(mTask =>
dct.chapter.sortNumber == nDct.chapter.sortNumber)) ((DownloadChapterTask)mTask).publication.internalId == ((DownloadChapterTask)newTask).publication.internalId &&
((DownloadChapterTask)mTask).connectorName == ((DownloadChapterTask)newTask).connectorName &&
((DownloadChapterTask)mTask).chapter.sortNumber == ((DownloadChapterTask)newTask).chapter.sortNumber))
_allTasks.Add(newTask); _allTasks.Add(newTask);
else else
logger?.WriteLine(this.GetType().ToString(), $"Task already exists {newTask}"); logger?.WriteLine(this.GetType().ToString(), $"Task already exists {newTask}");
@ -157,60 +179,130 @@ public class TaskManager
{ {
logger?.WriteLine(this.GetType().ToString(), $"Removing Task {removeTask}"); logger?.WriteLine(this.GetType().ToString(), $"Removing Task {removeTask}");
_allTasks.Remove(removeTask); _allTasks.Remove(removeTask);
removeTask.parentTask?.RemoveChildTask(removeTask); if (removeTask.parentTask is not null)
if (removeTask is DownloadChapterTask cRemoveTask && _runningDownloadChapterTasks.ContainsKey(cRemoveTask)) removeTask.parentTask.RemoveChildTask(removeTask);
if (removeTask.GetType() == typeof(DownloadChapterTask) && _runningDownloadChapterTasks.ContainsKey((DownloadChapterTask)removeTask))
{ {
_runningDownloadChapterTasks[cRemoveTask].Cancel(); _runningDownloadChapterTasks[(DownloadChapterTask)removeTask].Cancel();
_runningDownloadChapterTasks.Remove(cRemoveTask); _runningDownloadChapterTasks.Remove((DownloadChapterTask)removeTask);
} }
} }
public TrangaTask? AddTask(TrangaTask.Task taskType, string? connectorName, string? internalId,
TimeSpan reoccurrenceTime, string? language = "en")
{
TrangaTask? newTask = null;
switch (taskType)
{
case TrangaTask.Task.UpdateLibraries:
newTask = new UpdateLibrariesTask(taskType, reoccurrenceTime);
break;
case TrangaTask.Task.DownloadNewChapters:
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 == internalId);
newTask = new DownloadNewChaptersTask(taskType, connectorName!, publication, reoccurrenceTime, language!);
break;
}
if(newTask is not null)
AddTask(newTask);
return newTask;
}
/// <summary>
/// Removes Task from task-collection
/// </summary>
/// <param name="task">TrangaTask.Task type</param>
/// <param name="connectorName">Name of Connector that was used</param>
/// <param name="publicationId">Publication that was used</param>
public void DeleteTask(TrangaTask.Task task, string? connectorName, string? publicationId)
{
logger?.WriteLine(this.GetType().ToString(), $"Removing Task {task} {publicationId}");
switch (task)
{
case TrangaTask.Task.UpdateLibraries:
//Only one UpdateKomgaLibrary Task
logger?.WriteLine(this.GetType().ToString(), $"Removing old {task}-Task.");
_allTasks.RemoveWhere(trangaTask => trangaTask.task is TrangaTask.Task.UpdateLibraries);
break;
case TrangaTask.Task.DownloadNewChapters:
if (connectorName is null || publicationId is null)
logger?.WriteLine(this.GetType().ToString(), "connectorName and publication can not be null");
else
{
_allTasks.RemoveWhere(mTask =>
mTask.GetType() == typeof(DownloadNewChaptersTask) &&
((DownloadNewChaptersTask)mTask).publication.internalId == publicationId &&
((DownloadNewChaptersTask)mTask).connectorName == connectorName);
foreach(TrangaTask rTask in _allTasks.Where(mTask =>
mTask.GetType() == typeof(DownloadChapterTask) &&
((DownloadChapterTask)mTask).publication.internalId == publicationId &&
((DownloadChapterTask)mTask).connectorName == connectorName))
DeleteTask(rTask);
}
break;
}
ExportDataAndSettings();
}
public IEnumerable<TrangaTask> GetTasksMatching(TrangaTask.Task taskType, string? connectorName = null, string? searchString = null, string? internalId = null, string? chapterSortNumber = null) public IEnumerable<TrangaTask> GetTasksMatching(TrangaTask.Task taskType, string? connectorName = null, string? searchString = null, string? internalId = null, string? chapterSortNumber = null)
{ {
switch (taskType) switch (taskType)
{ {
case TrangaTask.Task.UpdateLibraries: case TrangaTask.Task.UpdateLibraries:
return _allTasks.Where(tTask => tTask.task == TrangaTask.Task.UpdateLibraries); return _allTasks.Where(tTask => tTask.task == TrangaTask.Task.UpdateLibraries);
case TrangaTask.Task.MonitorPublication: case TrangaTask.Task.DownloadNewChapters:
if(connectorName is null) if(connectorName is null)
return _allTasks.Where(tTask => tTask.task == taskType); return _allTasks.Where(tTask => tTask.task == taskType);
GetConnector(connectorName);//Name check GetConnector(connectorName);//Name check
IEnumerable<TrangaTask> matchingdnc = _allTasks.Where(tTask => tTask.GetType() == typeof(DownloadNewChaptersTask));
if (searchString is not null) if (searchString is not null)
{ {
return _allTasks.Where(mTask => return matchingdnc.Where(mTask =>
mTask is MonitorPublicationTask mpt && mpt.connectorName == connectorName && ((DownloadNewChaptersTask)mTask).connectorName == connectorName &&
mpt.ToString().Contains(searchString, StringComparison.InvariantCultureIgnoreCase)); ((DownloadNewChaptersTask)mTask).ToString().Contains(searchString, StringComparison.InvariantCultureIgnoreCase));
} }
else if (internalId is not null) else if (internalId is not null)
{ {
return _allTasks.Where(mTask => return matchingdnc.Where(mTask =>
mTask is MonitorPublicationTask mpt && mpt.connectorName == connectorName && ((DownloadNewChaptersTask)mTask).connectorName == connectorName &&
mpt.publication.internalId == internalId); ((DownloadNewChaptersTask)mTask).publication.internalId == internalId);
} }
else else
return _allTasks.Where(tTask => return _allTasks.Where(tTask =>
tTask is MonitorPublicationTask mpt && mpt.connectorName == connectorName); tTask.GetType() == typeof(DownloadNewChaptersTask) &&
((DownloadNewChaptersTask)tTask).connectorName == connectorName);
case TrangaTask.Task.DownloadChapter: case TrangaTask.Task.DownloadChapter:
if(connectorName is null) if(connectorName is null)
return _allTasks.Where(tTask => tTask.task == taskType); return _allTasks.Where(tTask => tTask.task == taskType);
GetConnector(connectorName);//Name check GetConnector(connectorName);//Name check
IEnumerable<TrangaTask> matchingdc = _allTasks.Where(tTask => tTask.GetType() == typeof(DownloadChapterTask));
if (searchString is not null) if (searchString is not null)
{ {
return _allTasks.Where(mTask => return matchingdc.Where(mTask =>
mTask is DownloadChapterTask dct && dct.connectorName == connectorName && ((DownloadChapterTask)mTask).connectorName == connectorName &&
dct.ToString().Contains(searchString, StringComparison.InvariantCultureIgnoreCase)); ((DownloadChapterTask)mTask).ToString().Contains(searchString, StringComparison.InvariantCultureIgnoreCase));
} }
else if (internalId is not null && chapterSortNumber is not null) else if (internalId is not null && chapterSortNumber is not null)
{ {
return _allTasks.Where(mTask => return matchingdc.Where(mTask =>
mTask is DownloadChapterTask dct && dct.connectorName == connectorName && ((DownloadChapterTask)mTask).connectorName == connectorName &&
dct.publication.publicationId == internalId && ((DownloadChapterTask)mTask).publication.publicationId == internalId &&
dct.chapter.sortNumber == chapterSortNumber); ((DownloadChapterTask)mTask).chapter.sortNumber == chapterSortNumber);
} }
else else
return _allTasks.Where(mTask => return _allTasks.Where(tTask =>
mTask is DownloadChapterTask dct && dct.connectorName == connectorName); tTask.GetType() == typeof(DownloadChapterTask) &&
((DownloadChapterTask)tTask).connectorName == connectorName);
default: default:
return Array.Empty<TrangaTask>(); return Array.Empty<TrangaTask>();

View File

@ -1,4 +1,5 @@
using System.Text.Json.Serialization; using System.Globalization;
using System.Text.Json.Serialization;
using Logging; using Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -10,7 +11,7 @@ namespace Tranga;
/// <summary> /// <summary>
/// Stores information on Task, when implementing new Tasks also update the serializer /// Stores information on Task, when implementing new Tasks also update the serializer
/// </summary> /// </summary>
[JsonDerivedType(typeof(MonitorPublicationTask), 2)] [JsonDerivedType(typeof(DownloadNewChaptersTask), 2)]
[JsonDerivedType(typeof(UpdateLibrariesTask), 3)] [JsonDerivedType(typeof(UpdateLibrariesTask), 3)]
[JsonDerivedType(typeof(DownloadChapterTask), 4)] [JsonDerivedType(typeof(DownloadChapterTask), 4)]
public abstract class TrangaTask public abstract class TrangaTask
@ -19,27 +20,27 @@ public abstract class TrangaTask
// ReSharper disable once MemberCanBePrivate.Global I want it thaaat way // ReSharper disable once MemberCanBePrivate.Global I want it thaaat way
public TimeSpan reoccurrence { get; } public TimeSpan reoccurrence { get; }
public DateTime lastExecuted { get; set; } public DateTime lastExecuted { get; set; }
[Newtonsoft.Json.JsonIgnore] public ExecutionState state { get; set; }
public Task task { get; } public Task task { get; }
public string taskId { get; } public string taskId { get; set; }
[Newtonsoft.Json.JsonIgnore] public ExecutionState state { get; set; }
[Newtonsoft.Json.JsonIgnore] protected HashSet<TrangaTask> childTasks { get; }
[Newtonsoft.Json.JsonIgnore] public TrangaTask? parentTask { get; set; } [Newtonsoft.Json.JsonIgnore] public TrangaTask? parentTask { get; set; }
public string? parentTaskId { get; set; } public string? parentTaskId { get; set; }
[Newtonsoft.Json.JsonIgnore] protected HashSet<TrangaTask> childTasks { get; } [Newtonsoft.Json.JsonIgnore]public double progress { get; private set; }
[Newtonsoft.Json.JsonIgnore] public double progress => GetProgress();
[Newtonsoft.Json.JsonIgnore]public DateTime executionStarted { get; private set; } [Newtonsoft.Json.JsonIgnore]public DateTime executionStarted { get; private set; }
[Newtonsoft.Json.JsonIgnore]public DateTime lastChange { get; private set; } [Newtonsoft.Json.JsonIgnore]public DateTime lastChange { get; private set; }
[Newtonsoft.Json.JsonIgnore]public DateTime executionApproximatelyFinished => progress != 0 ? lastChange.Add(GetRemainingTime()) : DateTime.MaxValue; [Newtonsoft.Json.JsonIgnore]public DateTime executionApproximatelyFinished => progress != 0 ? lastChange.Add(GetRemainingTime()) : DateTime.MaxValue;
[Newtonsoft.Json.JsonIgnore]public TimeSpan executionApproximatelyRemaining => executionApproximatelyFinished.Subtract(DateTime.Now); [Newtonsoft.Json.JsonIgnore]public TimeSpan executionApproximatelyRemaining => executionApproximatelyFinished.Subtract(DateTime.Now);
[Newtonsoft.Json.JsonIgnore]public DateTime nextExecution => lastExecuted.Add(reoccurrence); [Newtonsoft.Json.JsonIgnore]public DateTime nextExecution => lastExecuted.Add(reoccurrence);
public enum ExecutionState { Waiting, Enqueued, Running, Failed } public enum ExecutionState { Waiting, Enqueued, Running }
protected TrangaTask(Task task, TimeSpan reoccurrence, TrangaTask? parentTask = null) protected TrangaTask(Task task, TimeSpan reoccurrence, TrangaTask? parentTask = null)
{ {
this.reoccurrence = reoccurrence; this.reoccurrence = reoccurrence;
this.lastExecuted = DateTime.Now.Subtract(reoccurrence); this.lastExecuted = DateTime.Now.Subtract(reoccurrence);
this.task = task; this.task = task;
this.executionStarted = DateTime.UnixEpoch; this.executionStarted = DateTime.Now;
this.lastChange = DateTime.MaxValue; this.lastChange = DateTime.MaxValue;
this.taskId = Convert.ToBase64String(BitConverter.GetBytes(new Random().Next())); this.taskId = Convert.ToBase64String(BitConverter.GetBytes(new Random().Next()));
this.childTasks = new(); this.childTasks = new();
@ -53,11 +54,7 @@ public abstract class TrangaTask
/// <param name="taskManager"></param> /// <param name="taskManager"></param>
/// <param name="logger"></param> /// <param name="logger"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
protected abstract bool ExecuteTask(TaskManager taskManager, Logger? logger, CancellationToken? cancellationToken = null); protected abstract void ExecuteTask(TaskManager taskManager, Logger? logger, CancellationToken? cancellationToken = null);
public abstract TrangaTask Clone();
protected abstract double GetProgress();
/// <summary> /// <summary>
/// Execute the task /// Execute the task
@ -71,22 +68,20 @@ public abstract class TrangaTask
this.state = ExecutionState.Running; this.state = ExecutionState.Running;
this.executionStarted = DateTime.Now; this.executionStarted = DateTime.Now;
this.lastChange = DateTime.Now; this.lastChange = DateTime.Now;
bool success = ExecuteTask(taskManager, logger, cancellationToken); ExecuteTask(taskManager, logger, cancellationToken);
while(childTasks.Any(ct => ct.state is ExecutionState.Enqueued or ExecutionState.Running)) while(childTasks.Any(ct => ct.state is ExecutionState.Enqueued or ExecutionState.Running))
Thread.Sleep(1000); Thread.Sleep(1000);
if (success)
{
this.lastExecuted = DateTime.Now; this.lastExecuted = DateTime.Now;
this.state = ExecutionState.Waiting; this.state = ExecutionState.Waiting;
}
else
{
this.lastExecuted = DateTime.MaxValue;
this.state = ExecutionState.Failed;
}
logger?.WriteLine(this.GetType().ToString(), $"Finished Executing Task {this}"); logger?.WriteLine(this.GetType().ToString(), $"Finished Executing Task {this}");
} }
public void ReplaceFailedChildTask(DownloadChapterTask failed, DownloadChapterTask newTask)
{
this.RemoveChildTask(failed);
this.AddChildTask(newTask);
}
public void AddChildTask(TrangaTask childTask) public void AddChildTask(TrangaTask childTask)
{ {
this.childTasks.Add(childTask); this.childTasks.Add(childTask);
@ -95,22 +90,40 @@ public abstract class TrangaTask
public void RemoveChildTask(TrangaTask childTask) public void RemoveChildTask(TrangaTask childTask)
{ {
this.childTasks.Remove(childTask); this.childTasks.Remove(childTask);
this.DecrementProgress(childTask.progress);
}
public void IncrementProgress(double amount)
{
this.lastChange = DateTime.Now;
this.progress += amount / (childTasks.Count > 0 ? childTasks.Count : 1);
if (parentTask is not null)
{
parentTask.IncrementProgress(amount);
parentTask.state = ExecutionState.Running;
}
}
public void DecrementProgress(double amount)
{
this.lastChange = DateTime.Now;
this.progress -= amount / childTasks.Count > 0 ? childTasks.Count : 1;
parentTask?.DecrementProgress(amount);
} }
private TimeSpan GetRemainingTime() private TimeSpan GetRemainingTime()
{ {
if(progress == 0 || lastChange > executionStarted) if(progress == 0)
return DateTime.MaxValue.Subtract(DateTime.Now.AddYears(1)); return DateTime.MaxValue.Subtract(DateTime.Now);
TimeSpan elapsed = lastChange.Subtract(executionStarted); TimeSpan elapsed = lastChange.Subtract(executionStarted);
return elapsed.Divide(progress).Subtract(elapsed); return elapsed.Divide(progress).Subtract(elapsed);
} }
public enum Task : byte public enum Task : byte
{ {
MonitorPublication = 2, DownloadNewChapters = 2,
UpdateLibraries = 3, UpdateLibraries = 3,
DownloadChapter = 4, DownloadChapter = 4
DownloadNewChapters = 2 //legacy
} }
public override string ToString() public override string ToString()
@ -122,14 +135,14 @@ public abstract class TrangaTask
{ {
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
{ {
return objectType == typeof(TrangaTask); return (objectType == typeof(TrangaTask));
} }
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{ {
JObject jo = JObject.Load(reader); JObject jo = JObject.Load(reader);
if (jo["task"]!.Value<Int64>() == (Int64)Task.MonitorPublication) if (jo["task"]!.Value<Int64>() == (Int64)Task.DownloadNewChapters)
return jo.ToObject<MonitorPublicationTask>(serializer)!; return jo.ToObject<DownloadNewChaptersTask>(serializer)!;
if (jo["task"]!.Value<Int64>() == (Int64)Task.UpdateLibraries) if (jo["task"]!.Value<Int64>() == (Int64)Task.UpdateLibraries)
return jo.ToObject<UpdateLibrariesTask>(serializer)!; return jo.ToObject<UpdateLibrariesTask>(serializer)!;

View File

@ -1,4 +1,5 @@
using Logging; using Logging;
using Newtonsoft.Json;
namespace Tranga.TrangaTasks; namespace Tranga.TrangaTasks;
@ -9,9 +10,7 @@ public class DownloadChapterTask : TrangaTask
public string language { get; } public string language { get; }
public Chapter chapter { get; } public Chapter chapter { get; }
private double _dctProgress = 0; public DownloadChapterTask(Task task, string connectorName, Publication publication, Chapter chapter, string language = "en", DownloadNewChaptersTask? parentTask = null) : base(task, TimeSpan.Zero, parentTask)
public DownloadChapterTask(string connectorName, Publication publication, Chapter chapter, string language = "en", MonitorPublicationTask? parentTask = null) : base(Task.DownloadChapter, TimeSpan.Zero, parentTask)
{ {
this.chapter = chapter; this.chapter = chapter;
this.connectorName = connectorName; this.connectorName = connectorName;
@ -19,34 +18,21 @@ public class DownloadChapterTask : TrangaTask
this.language = language; this.language = language;
} }
protected override bool ExecuteTask(TaskManager taskManager, Logger? logger, CancellationToken? cancellationToken = null) protected override void ExecuteTask(TaskManager taskManager, Logger? logger, CancellationToken? cancellationToken = null)
{ {
if (cancellationToken?.IsCancellationRequested??false) if (cancellationToken?.IsCancellationRequested??false)
return false; return;
if(this.parentTask is not null)
this.parentTask.state = ExecutionState.Running;
Connector connector = taskManager.GetConnector(this.connectorName); Connector connector = taskManager.GetConnector(this.connectorName);
connector.CopyCoverFromCacheToDownloadLocation(this.publication, taskManager.settings); connector.CopyCoverFromCacheToDownloadLocation(this.publication, taskManager.settings);
bool downloadSuccess = connector.DownloadChapter(this.publication, this.chapter, this, cancellationToken); bool downloadSuccess = connector.DownloadChapter(this.publication, this.chapter, this, cancellationToken);
if(this.parentTask is not null)
this.parentTask.state = ExecutionState.Waiting;
taskManager.DeleteTask(this); taskManager.DeleteTask(this);
if(downloadSuccess && parentTask is not null) if(downloadSuccess && parentTask is not null)
foreach(NotificationManager nm in taskManager.settings.notificationManagers) foreach(NotificationManager nm in taskManager.settings.notificationManagers)
nm.SendNotification("New Chapter downloaded", $"{this.publication.sortName} {this.chapter.chapterNumber} {this.chapter.name}"); nm.SendNotification("New Chapter downloaded", $"{this.publication.sortName} {this.chapter.chapterNumber} {this.chapter.name}");
return downloadSuccess;
}
public override TrangaTask Clone()
{
return new DownloadChapterTask(this.connectorName, this.publication, this.chapter,
this.language, (MonitorPublicationTask?)this.parentTask);
}
protected override double GetProgress()
{
return _dctProgress;
}
internal void IncrementProgress(double amount)
{
this._dctProgress += amount;
} }
public override string ToString() public override string ToString()

View File

@ -0,0 +1,45 @@
using Logging;
namespace Tranga.TrangaTasks;
public class DownloadNewChaptersTask : TrangaTask
{
public string connectorName { get; }
public Publication publication { get; }
public string language { get; }
public DownloadNewChaptersTask(Task task, string connectorName, Publication publication, TimeSpan reoccurrence, string language = "en") : base(task, reoccurrence)
{
this.connectorName = connectorName;
this.publication = publication;
this.language = language;
}
protected override void ExecuteTask(TaskManager taskManager, Logger? logger, CancellationToken? cancellationToken = null)
{
if (cancellationToken?.IsCancellationRequested??false)
return;
Publication pub = publication!;
Connector connector = taskManager.GetConnector(this.connectorName);
//Check if Publication already has a Folder
pub.CreatePublicationFolder(taskManager.settings.downloadLocation);
List<Chapter> newChapters = taskManager.GetNewChaptersList(connector, pub, language!);
connector.CopyCoverFromCacheToDownloadLocation(pub, taskManager.settings);
pub.SaveSeriesInfoJson(connector.downloadLocation);
foreach (Chapter newChapter in newChapters)
{
DownloadChapterTask newTask = new (Task.DownloadChapter, this.connectorName, pub, newChapter, this.language, this);
this.childTasks.Add(newTask);
newTask.state = ExecutionState.Enqueued;
taskManager.AddTask(newTask);
}
}
public override string ToString()
{
return $"{base.ToString()}, {connectorName}, {publication.sortName} {publication.internalId}";
}
}

View File

@ -1,59 +0,0 @@
using Logging;
namespace Tranga.TrangaTasks;
public class MonitorPublicationTask : TrangaTask
{
public string connectorName { get; }
public Publication publication { get; }
public string language { get; }
public MonitorPublicationTask(string connectorName, Publication publication, TimeSpan reoccurrence, string language = "en") : base(Task.MonitorPublication, reoccurrence)
{
this.connectorName = connectorName;
this.publication = publication;
this.language = language;
}
protected override bool ExecuteTask(TaskManager taskManager, Logger? logger, CancellationToken? cancellationToken = null)
{
if (cancellationToken?.IsCancellationRequested??false)
return false;
Connector connector = taskManager.GetConnector(this.connectorName);
//Check if Publication already has a Folder
publication.CreatePublicationFolder(taskManager.settings.downloadLocation);
List<Chapter> newChapters = taskManager.GetNewChaptersList(connector, publication, language);
connector.CopyCoverFromCacheToDownloadLocation(publication, taskManager.settings);
publication.SaveSeriesInfoJson(connector.downloadLocation);
foreach (Chapter newChapter in newChapters)
{
DownloadChapterTask newTask = new (this.connectorName, publication, newChapter, this.language, this);
this.childTasks.Add(newTask);
newTask.state = ExecutionState.Enqueued;
taskManager.AddTask(newTask);
}
return true;
}
public override TrangaTask Clone()
{
return new MonitorPublicationTask(this.connectorName, this.publication, this.reoccurrence,
this.language);
}
protected override double GetProgress()
{
if (this.childTasks.Count > 0)
return this.childTasks.Sum(ct => ct.progress) / childTasks.Count;
return 1;
}
public override string ToString()
{
return $"{base.ToString()}, {connectorName}, {publication.sortName} {publication.internalId}";
}
}

View File

@ -4,26 +4,16 @@ namespace Tranga.TrangaTasks;
public class UpdateLibrariesTask : TrangaTask public class UpdateLibrariesTask : TrangaTask
{ {
public UpdateLibrariesTask(TimeSpan reoccurrence) : base(Task.UpdateLibraries, reoccurrence) public UpdateLibrariesTask(Task task, TimeSpan reoccurrence) : base(task, reoccurrence)
{ {
} }
protected override bool ExecuteTask(TaskManager taskManager, Logger? logger, CancellationToken? cancellationToken = null) protected override void ExecuteTask(TaskManager taskManager, Logger? logger, CancellationToken? cancellationToken = null)
{ {
if (cancellationToken?.IsCancellationRequested??false) if (cancellationToken?.IsCancellationRequested??false)
return false; return;
foreach(LibraryManager lm in taskManager.settings.libraryManagers) foreach(LibraryManager lm in taskManager.settings.libraryManagers)
lm.UpdateLibrary(); lm.UpdateLibrary();
return true; IncrementProgress(1);
}
public override TrangaTask Clone()
{
return new UpdateLibrariesTask(this.reoccurrence);
}
protected override double GetProgress()
{
return 1;
} }
} }