266 lines
8.1 KiB
C#
266 lines
8.1 KiB
C#
using ApplicationServices;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Threading;
|
|
using DataLayer;
|
|
using LibationFileManager;
|
|
using ReactiveUI;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace LibationAvalonia.ViewModels
|
|
{
|
|
public class ProcessQueueViewModel : ViewModelBase, ILogForm
|
|
{
|
|
public ObservableCollection<LogEntry> LogEntries { get; } = new();
|
|
public TrackedQueue<ProcessBookViewModel> Items { get; } = new();
|
|
|
|
private TrackedQueue<ProcessBookViewModel> Queue => Items;
|
|
public ProcessBookViewModel SelectedItem { get; set; }
|
|
public Task QueueRunner { get; private set; }
|
|
public bool Running => !QueueRunner?.IsCompleted ?? false;
|
|
|
|
private readonly LogMe Logger;
|
|
|
|
public ProcessQueueViewModel()
|
|
{
|
|
Logger = LogMe.RegisterForm(this);
|
|
Queue.QueuededCountChanged += Queue_QueuededCountChanged;
|
|
Queue.CompletedCountChanged += Queue_CompletedCountChanged;
|
|
|
|
if (Design.IsDesignMode)
|
|
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists();
|
|
|
|
SpeedLimit = Configuration.Instance.DownloadSpeedLimit / 1024m / 1024;
|
|
}
|
|
|
|
private int _completedCount;
|
|
private int _errorCount;
|
|
private int _queuedCount;
|
|
private string _runningTime;
|
|
private bool _progressBarVisible;
|
|
private decimal _speedLimit;
|
|
|
|
public int CompletedCount { get => _completedCount; private set { this.RaiseAndSetIfChanged(ref _completedCount, value); this.RaisePropertyChanged(nameof(AnyCompleted)); } }
|
|
public int QueuedCount { get => _queuedCount; private set { this.RaiseAndSetIfChanged(ref _queuedCount, value); this.RaisePropertyChanged(nameof(AnyQueued)); } }
|
|
public int ErrorCount { get => _errorCount; private set { this.RaiseAndSetIfChanged(ref _errorCount, value); this.RaisePropertyChanged(nameof(AnyErrors)); } }
|
|
public string RunningTime { get => _runningTime; set { this.RaiseAndSetIfChanged(ref _runningTime, value); } }
|
|
public bool ProgressBarVisible { get => _progressBarVisible; set { this.RaiseAndSetIfChanged(ref _progressBarVisible, value); } }
|
|
public bool AnyCompleted => CompletedCount > 0;
|
|
public bool AnyQueued => QueuedCount > 0;
|
|
public bool AnyErrors => ErrorCount > 0;
|
|
public double Progress => 100d * Queue.Completed.Count / Queue.Count;
|
|
|
|
public decimal SpeedLimit
|
|
{
|
|
get
|
|
{
|
|
return _speedLimit;
|
|
}
|
|
set
|
|
{
|
|
var newValue = Math.Min(999 * 1024 * 1024, (long)(value * 1024 * 1024));
|
|
var config = Configuration.Instance;
|
|
config.DownloadSpeedLimit = newValue;
|
|
|
|
_speedLimit
|
|
= config.DownloadSpeedLimit <= newValue ? value
|
|
: value == 0.01m ? config.DownloadSpeedLimit / 1024m / 1024
|
|
: 0;
|
|
|
|
config.DownloadSpeedLimit = (long)(_speedLimit * 1024 * 1024);
|
|
|
|
SpeedLimitIncrement = _speedLimit > 100 ? 10
|
|
: _speedLimit > 10 ? 1
|
|
: _speedLimit > 1 ? 0.1m
|
|
: 0.01m;
|
|
|
|
this.RaisePropertyChanged(nameof(SpeedLimitIncrement));
|
|
this.RaisePropertyChanged();
|
|
}
|
|
}
|
|
|
|
public decimal SpeedLimitIncrement { get; private set; }
|
|
|
|
private void Queue_CompletedCountChanged(object sender, int e)
|
|
{
|
|
int errCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.FailedAbort or ProcessBookResult.FailedSkip or ProcessBookResult.FailedRetry or ProcessBookResult.ValidationFail);
|
|
int completeCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.Success);
|
|
|
|
ErrorCount = errCount;
|
|
CompletedCount = completeCount;
|
|
this.RaisePropertyChanged(nameof(Progress));
|
|
}
|
|
private void Queue_QueuededCountChanged(object sender, int cueCount)
|
|
{
|
|
QueuedCount = cueCount;
|
|
this.RaisePropertyChanged(nameof(Progress));
|
|
}
|
|
|
|
public void WriteLine(string text)
|
|
{
|
|
Dispatcher.UIThread.Post(() =>
|
|
LogEntries.Add(new()
|
|
{
|
|
LogDate = DateTime.Now,
|
|
LogMessage = text.Trim()
|
|
}));
|
|
}
|
|
|
|
|
|
#region Add Books to Queue
|
|
|
|
private bool isBookInQueue(LibraryBook libraryBook)
|
|
=> Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId);
|
|
|
|
public void AddDownloadPdf(LibraryBook libraryBook)
|
|
=> AddDownloadPdf(new List<LibraryBook>() { libraryBook });
|
|
|
|
public void AddDownloadDecrypt(LibraryBook libraryBook)
|
|
=> AddDownloadDecrypt(new List<LibraryBook>() { libraryBook });
|
|
|
|
public void AddConvertMp3(LibraryBook libraryBook)
|
|
=> AddConvertMp3(new List<LibraryBook>() { libraryBook });
|
|
|
|
public void AddDownloadPdf(IEnumerable<LibraryBook> entries)
|
|
{
|
|
List<ProcessBookViewModel> procs = new();
|
|
foreach (var entry in entries)
|
|
{
|
|
if (isBookInQueue(entry))
|
|
continue;
|
|
|
|
ProcessBookViewModel pbook = new(entry, Logger);
|
|
pbook.AddDownloadPdf();
|
|
procs.Add(pbook);
|
|
}
|
|
|
|
Serilog.Log.Logger.Information("Queueing {count} books", procs.Count);
|
|
AddToQueue(procs);
|
|
}
|
|
|
|
public void AddDownloadDecrypt(IEnumerable<LibraryBook> entries)
|
|
{
|
|
List<ProcessBookViewModel> procs = new();
|
|
foreach (var entry in entries)
|
|
{
|
|
if (isBookInQueue(entry))
|
|
continue;
|
|
|
|
ProcessBookViewModel pbook = new(entry, Logger);
|
|
pbook.AddDownloadDecryptBook();
|
|
pbook.AddDownloadPdf();
|
|
procs.Add(pbook);
|
|
}
|
|
|
|
Serilog.Log.Logger.Information("Queueing {count} books", procs.Count);
|
|
AddToQueue(procs);
|
|
}
|
|
|
|
public void AddConvertMp3(IEnumerable<LibraryBook> entries)
|
|
{
|
|
List<ProcessBookViewModel> procs = new();
|
|
foreach (var entry in entries)
|
|
{
|
|
if (isBookInQueue(entry))
|
|
continue;
|
|
|
|
ProcessBookViewModel pbook = new(entry, Logger);
|
|
pbook.AddConvertToMp3();
|
|
procs.Add(pbook);
|
|
}
|
|
|
|
Serilog.Log.Logger.Information("Queueing {count} books", procs.Count);
|
|
AddToQueue(procs);
|
|
}
|
|
|
|
public void AddToQueue(IEnumerable<ProcessBookViewModel> pbook)
|
|
{
|
|
Dispatcher.UIThread.Post(() =>
|
|
{
|
|
Queue.Enqueue(pbook);
|
|
if (!Running)
|
|
QueueRunner = QueueLoop();
|
|
});
|
|
}
|
|
|
|
#endregion
|
|
|
|
DateTime StartingTime;
|
|
private async Task QueueLoop()
|
|
{
|
|
try
|
|
{
|
|
Serilog.Log.Logger.Information("Begin processing queue");
|
|
|
|
RunningTime = string.Empty;
|
|
ProgressBarVisible = true;
|
|
StartingTime = DateTime.Now;
|
|
|
|
using var counterTimer = new System.Threading.Timer(CounterTimer_Tick, null, 0, 500);
|
|
|
|
bool shownServiceOutageMessage = false;
|
|
|
|
while (Queue.MoveNext())
|
|
{
|
|
var nextBook = Queue.Current;
|
|
|
|
Serilog.Log.Logger.Information("Begin processing queued item. {item_LibraryBook}", nextBook?.LibraryBook);
|
|
|
|
var result = await nextBook.ProcessOneAsync();
|
|
|
|
Serilog.Log.Logger.Information("Completed processing queued item: {item_LibraryBook}\r\nResult: {result}", nextBook?.LibraryBook, result);
|
|
|
|
if (result == ProcessBookResult.ValidationFail)
|
|
Queue.ClearCurrent();
|
|
else if (result == ProcessBookResult.FailedAbort)
|
|
Queue.ClearQueue();
|
|
else if (result == ProcessBookResult.FailedSkip)
|
|
nextBook.LibraryBook.Book.UpdateBookStatus(LiberatedStatus.Error);
|
|
else if (result == ProcessBookResult.LicenseDeniedPossibleOutage && !shownServiceOutageMessage)
|
|
{
|
|
await MessageBox.Show(@$"
|
|
You were denied a content license for {nextBook.LibraryBook.Book.Title}
|
|
|
|
This error appears to be caused by a temporary interruption of service that sometimes affects Libation's users. This type of error usually resolves itself in 1 to 2 days, and in the meantime you should still be able to access your books through Audible's website or app.
|
|
",
|
|
"Possible Interruption of Service",
|
|
MessageBoxButtons.OK,
|
|
MessageBoxIcon.Asterisk);
|
|
shownServiceOutageMessage = true;
|
|
}
|
|
}
|
|
Serilog.Log.Logger.Information("Completed processing queue");
|
|
|
|
Queue_CompletedCountChanged(this, 0);
|
|
ProgressBarVisible = false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Serilog.Log.Logger.Error(ex, "An error was encountered while processing queued items");
|
|
}
|
|
}
|
|
|
|
private void CounterTimer_Tick(object state)
|
|
{
|
|
string timeToStr(TimeSpan time)
|
|
{
|
|
string minsSecs = $"{time:mm\\:ss}";
|
|
if (time.TotalHours >= 1)
|
|
return $"{time.TotalHours:F0}:{minsSecs}";
|
|
return minsSecs;
|
|
}
|
|
RunningTime = timeToStr(DateTime.Now - StartingTime);
|
|
}
|
|
}
|
|
|
|
public class LogEntry
|
|
{
|
|
public DateTime LogDate { get; init; }
|
|
public string LogDateString => LogDate.ToShortTimeString();
|
|
public string LogMessage { get; init; }
|
|
}
|
|
}
|