Merge pull request #413 from Mbucari/master
Update obsolete code and fix #347
This commit is contained in:
commit
7fd002d2c9
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
@ -52,7 +51,7 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
// delete file after validation is complete
|
// delete file after validation is complete
|
||||||
FileUtility.SaferDelete(OutputFileName);
|
FileUtility.SaferDelete(OutputFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Task CancelAsync();
|
public abstract Task CancelAsync();
|
||||||
|
|
||||||
@ -72,7 +71,7 @@ namespace AaxDecrypter
|
|||||||
=> RetrievedNarrators?.Invoke(this, narrators);
|
=> RetrievedNarrators?.Invoke(this, narrators);
|
||||||
protected void OnRetrievedCoverArt(byte[] coverArt)
|
protected void OnRetrievedCoverArt(byte[] coverArt)
|
||||||
=> RetrievedCoverArt?.Invoke(this, coverArt);
|
=> RetrievedCoverArt?.Invoke(this, coverArt);
|
||||||
protected void OnDecryptProgressUpdate(DownloadProgress downloadProgress)
|
protected void OnDecryptProgressUpdate(DownloadProgress downloadProgress)
|
||||||
=> DecryptProgressUpdate?.Invoke(this, downloadProgress);
|
=> DecryptProgressUpdate?.Invoke(this, downloadProgress);
|
||||||
protected void OnDecryptTimeRemaining(TimeSpan timeRemaining)
|
protected void OnDecryptTimeRemaining(TimeSpan timeRemaining)
|
||||||
=> DecryptTimeRemaining?.Invoke(this, timeRemaining);
|
=> DecryptTimeRemaining?.Invoke(this, timeRemaining);
|
||||||
@ -111,8 +110,8 @@ namespace AaxDecrypter
|
|||||||
{
|
{
|
||||||
FileUtility.SaferDelete(jsonDownloadState);
|
FileUtility.SaferDelete(jsonDownloadState);
|
||||||
|
|
||||||
if (DownloadOptions.AudibleKey is not null &&
|
if (DownloadOptions.AudibleKey is not null &&
|
||||||
DownloadOptions.AudibleIV is not null &&
|
DownloadOptions.AudibleIV is not null &&
|
||||||
DownloadOptions.RetainEncryptedFile)
|
DownloadOptions.RetainEncryptedFile)
|
||||||
{
|
{
|
||||||
string aaxPath = Path.ChangeExtension(TempFilePath, ".aax");
|
string aaxPath = Path.ChangeExtension(TempFilePath, ".aax");
|
||||||
@ -156,12 +155,7 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
private NetworkFileStreamPersister NewNetworkFilePersister()
|
private NetworkFileStreamPersister NewNetworkFilePersister()
|
||||||
{
|
{
|
||||||
var headers = new System.Net.WebHeaderCollection
|
var networkFileStream = new NetworkFileStream(TempFilePath, new Uri(DownloadOptions.DownloadUrl), 0, new() { { "User-Agent", DownloadOptions.UserAgent } });
|
||||||
{
|
|
||||||
{ "User-Agent", DownloadOptions.UserAgent }
|
|
||||||
};
|
|
||||||
|
|
||||||
var networkFileStream = new NetworkFileStream(TempFilePath, new Uri(DownloadOptions.DownloadUrl), 0, headers);
|
|
||||||
return new NetworkFileStreamPersister(networkFileStream, jsonDownloadState);
|
return new NetworkFileStreamPersister(networkFileStream, jsonDownloadState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,91 +1,53 @@
|
|||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace AaxDecrypter
|
namespace AaxDecrypter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>A resumable, simultaneous file downloader and reader. </summary>
|
||||||
/// A <see cref="CookieContainer"/> for a single Uri.
|
|
||||||
/// </summary>
|
|
||||||
public class SingleUriCookieContainer : CookieContainer
|
|
||||||
{
|
|
||||||
private Uri baseAddress;
|
|
||||||
public Uri Uri
|
|
||||||
{
|
|
||||||
get => baseAddress;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
baseAddress = new UriBuilder(value.Scheme, value.Host).Uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CookieCollection GetCookies()
|
|
||||||
{
|
|
||||||
return GetCookies(Uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A resumable, simultaneous file downloader and reader.
|
|
||||||
/// </summary>
|
|
||||||
public class NetworkFileStream : Stream, IUpdatable
|
public class NetworkFileStream : Stream, IUpdatable
|
||||||
{
|
{
|
||||||
public event EventHandler Updated;
|
public event EventHandler Updated;
|
||||||
|
|
||||||
#region Public Properties
|
#region Public Properties
|
||||||
|
|
||||||
/// <summary>
|
/// <summary> Location to save the downloaded data. </summary>
|
||||||
/// Location to save the downloaded data.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonProperty(Required = Required.Always)]
|
||||||
public string SaveFilePath { get; }
|
public string SaveFilePath { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary> Http(s) address of the file to download. </summary>
|
||||||
/// Http(s) address of the file to download.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonProperty(Required = Required.Always)]
|
||||||
public Uri Uri { get; private set; }
|
public Uri Uri { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary> Http headers to be sent to the server with the request. </summary>
|
||||||
/// All cookies set by caller or by the remote server.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonProperty(Required = Required.Always)]
|
||||||
public SingleUriCookieContainer CookieContainer { get; }
|
public Dictionary<string, string> RequestHeaders { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary> The position in <see cref="SaveFilePath"/> that has been written and flushed to disk. </summary>
|
||||||
/// Http headers to be sent to the server with the request.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
|
||||||
public WebHeaderCollection RequestHeaders { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The position in <see cref="SaveFilePath"/> that has been written and flushed to disk.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonProperty(Required = Required.Always)]
|
||||||
public long WritePosition { get; private set; }
|
public long WritePosition { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary> The total length of the <see cref="Uri"/> file to download. </summary>
|
||||||
/// The total length of the <see cref="Uri"/> file to download.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonProperty(Required = Required.Always)]
|
||||||
public long ContentLength { get; private set; }
|
public long ContentLength { get; private set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool IsCancelled { get; private set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Private Properties
|
#region Private Properties
|
||||||
private HttpWebRequest HttpRequest { get; set; }
|
|
||||||
private FileStream _writeFile { get; }
|
private FileStream _writeFile { get; }
|
||||||
private FileStream _readFile { get; }
|
private FileStream _readFile { get; }
|
||||||
private Stream _networkStream { get; set; }
|
private EventWaitHandle _downloadedPiece { get; set; }
|
||||||
private bool hasBegunDownloading { get; set; }
|
private Task _backgroundDownloadTask { get; set; }
|
||||||
public bool IsCancelled { get; private set; }
|
|
||||||
private EventWaitHandle downloadEnded { get; set; }
|
|
||||||
private EventWaitHandle downloadedPiece { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -102,15 +64,12 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
#region Constructor
|
#region Constructor
|
||||||
|
|
||||||
/// <summary>
|
/// <summary> A resumable, simultaneous file downloader and reader. </summary>
|
||||||
/// A resumable, simultaneous file downloader and reader.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="saveFilePath">Path to a location on disk to save the downloaded data from <paramref name="uri"/></param>
|
/// <param name="saveFilePath">Path to a location on disk to save the downloaded data from <paramref name="uri"/></param>
|
||||||
/// <param name="uri">Http(s) address of the file to download.</param>
|
/// <param name="uri">Http(s) address of the file to download.</param>
|
||||||
/// <param name="writePosition">The position in <paramref name="uri"/> to begin downloading.</param>
|
/// <param name="writePosition">The position in <paramref name="uri"/> to begin downloading.</param>
|
||||||
/// <param name="requestHeaders">Http headers to be sent to the server with the <see cref="HttpWebRequest"/>.</param>
|
/// <param name="requestHeaders">Http headers to be sent to the server with the <see cref="HttpWebRequest"/>.</param>
|
||||||
/// <param name="cookies">A <see cref="SingleUriCookieContainer"/> with cookies to send with the <see cref="HttpWebRequest"/>. It will also be populated with any cookies set by the server. </param>
|
public NetworkFileStream(string saveFilePath, Uri uri, long writePosition = 0, Dictionary<string, string> requestHeaders = null)
|
||||||
public NetworkFileStream(string saveFilePath, Uri uri, long writePosition = 0, WebHeaderCollection requestHeaders = null, SingleUriCookieContainer cookies = null)
|
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(saveFilePath, nameof(saveFilePath));
|
ArgumentValidator.EnsureNotNullOrWhiteSpace(saveFilePath, nameof(saveFilePath));
|
||||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(uri?.AbsoluteUri, nameof(uri));
|
ArgumentValidator.EnsureNotNullOrWhiteSpace(uri?.AbsoluteUri, nameof(uri));
|
||||||
@ -122,8 +81,7 @@ namespace AaxDecrypter
|
|||||||
SaveFilePath = saveFilePath;
|
SaveFilePath = saveFilePath;
|
||||||
Uri = uri;
|
Uri = uri;
|
||||||
WritePosition = writePosition;
|
WritePosition = writePosition;
|
||||||
RequestHeaders = requestHeaders ?? new WebHeaderCollection();
|
RequestHeaders = requestHeaders ?? new();
|
||||||
CookieContainer = cookies ?? new SingleUriCookieContainer { Uri = uri };
|
|
||||||
|
|
||||||
_writeFile = new FileStream(SaveFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite)
|
_writeFile = new FileStream(SaveFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite)
|
||||||
{
|
{
|
||||||
@ -139,12 +97,10 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
#region Downloader
|
#region Downloader
|
||||||
|
|
||||||
/// <summary>
|
/// <summary> Update the <see cref="JsonFilePersister"/>. </summary>
|
||||||
/// Update the <see cref="JsonFilePersister"/>.
|
|
||||||
/// </summary>
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
RequestHeaders = HttpRequest.Headers;
|
RequestHeaders["Range"] = $"bytes={WritePosition}-";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Updated?.Invoke(this, EventArgs.Empty);
|
Updated?.Invoke(this, EventArgs.Empty);
|
||||||
@ -155,9 +111,7 @@ namespace AaxDecrypter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary> Set a different <see cref="System.Uri"/> to the same file targeted by this instance of <see cref="NetworkFileStream"/> </summary>
|
||||||
/// Set a different <see cref="System.Uri"/> to the same file targeted by this instance of <see cref="NetworkFileStream"/>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uriToSameFile">New <see cref="System.Uri"/> host must match existing host.</param>
|
/// <param name="uriToSameFile">New <see cref="System.Uri"/> host must match existing host.</param>
|
||||||
public void SetUriForSameFile(Uri uriToSameFile)
|
public void SetUriForSameFile(Uri uriToSameFile)
|
||||||
{
|
{
|
||||||
@ -165,37 +119,31 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
if (uriToSameFile.Host != Uri.Host)
|
if (uriToSameFile.Host != Uri.Host)
|
||||||
throw new ArgumentException($"New uri to the same file must have the same host.\r\n Old Host :{Uri.Host}\r\nNew Host: {uriToSameFile.Host}");
|
throw new ArgumentException($"New uri to the same file must have the same host.\r\n Old Host :{Uri.Host}\r\nNew Host: {uriToSameFile.Host}");
|
||||||
if (hasBegunDownloading)
|
if (_backgroundDownloadTask is not null)
|
||||||
throw new InvalidOperationException("Cannot change Uri after download has started.");
|
throw new InvalidOperationException("Cannot change Uri after download has started.");
|
||||||
|
|
||||||
Uri = uriToSameFile;
|
Uri = uriToSameFile;
|
||||||
HttpRequest = WebRequest.CreateHttp(Uri);
|
RequestHeaders["Range"] = $"bytes={WritePosition}-";
|
||||||
|
|
||||||
HttpRequest.CookieContainer = CookieContainer;
|
|
||||||
HttpRequest.Headers = RequestHeaders;
|
|
||||||
//If NetworkFileStream is resuming, Header will already contain a range.
|
|
||||||
HttpRequest.Headers.Remove("Range");
|
|
||||||
HttpRequest.AddRange(WritePosition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Begins downloading <see cref="Uri"/> to <see cref="SaveFilePath"/> in a background thread.
|
|
||||||
/// </summary>
|
|
||||||
private void BeginDownloading()
|
|
||||||
{
|
|
||||||
downloadEnded = new EventWaitHandle(false, EventResetMode.ManualReset);
|
|
||||||
|
|
||||||
|
/// <summary> Begins downloading <see cref="Uri"/> to <see cref="SaveFilePath"/> in a background thread. </summary>
|
||||||
|
/// <returns>The downloader <see cref="Task"/></returns>
|
||||||
|
private Task BeginDownloading()
|
||||||
|
{
|
||||||
if (ContentLength != 0 && WritePosition == ContentLength)
|
if (ContentLength != 0 && WritePosition == ContentLength)
|
||||||
{
|
return Task.CompletedTask;
|
||||||
hasBegunDownloading = true;
|
|
||||||
downloadEnded.Set();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ContentLength != 0 && WritePosition > ContentLength)
|
if (ContentLength != 0 && WritePosition > ContentLength)
|
||||||
throw new WebException($"Specified write position (0x{WritePosition:X10}) is larger than {nameof(ContentLength)} (0x{ContentLength:X10}).");
|
throw new WebException($"Specified write position (0x{WritePosition:X10}) is larger than {nameof(ContentLength)} (0x{ContentLength:X10}).");
|
||||||
|
|
||||||
var response = HttpRequest.GetResponse() as HttpWebResponse;
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, Uri);
|
||||||
|
|
||||||
|
foreach (var header in RequestHeaders)
|
||||||
|
request.Headers.Add(header.Key, header.Value);
|
||||||
|
|
||||||
|
var response = new HttpClient().Send(request, HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
if (response.StatusCode != HttpStatusCode.PartialContent)
|
if (response.StatusCode != HttpStatusCode.PartialContent)
|
||||||
throw new WebException($"Server at {Uri.Host} responded with unexpected status code: {response.StatusCode}.");
|
throw new WebException($"Server at {Uri.Host} responded with unexpected status code: {response.StatusCode}.");
|
||||||
@ -203,24 +151,17 @@ namespace AaxDecrypter
|
|||||||
//Content length is the length of the range request, and it is only equal
|
//Content length is the length of the range request, and it is only equal
|
||||||
//to the complete file length if requesting Range: bytes=0-
|
//to the complete file length if requesting Range: bytes=0-
|
||||||
if (WritePosition == 0)
|
if (WritePosition == 0)
|
||||||
ContentLength = response.ContentLength;
|
ContentLength = response.Content.Headers.ContentLength.GetValueOrDefault();
|
||||||
|
|
||||||
_networkStream = response.GetResponseStream();
|
var networkStream = response.Content.ReadAsStream();
|
||||||
downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset);
|
_downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset);
|
||||||
|
|
||||||
//Download the file in the background.
|
//Download the file in the background.
|
||||||
new Thread(() => DownloadFile())
|
return Task.Run(() => DownloadFile(networkStream));
|
||||||
{ IsBackground = true }
|
|
||||||
.Start();
|
|
||||||
|
|
||||||
hasBegunDownloading = true;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary> Download <see cref="Uri"/> to <see cref="SaveFilePath"/>.</summary>
|
||||||
/// Download <see cref="Uri"/> to <see cref="SaveFilePath"/>.
|
private void DownloadFile(Stream networkStream)
|
||||||
/// </summary>
|
|
||||||
private void DownloadFile()
|
|
||||||
{
|
{
|
||||||
var downloadPosition = WritePosition;
|
var downloadPosition = WritePosition;
|
||||||
var nextFlush = downloadPosition + DATA_FLUSH_SZ;
|
var nextFlush = downloadPosition + DATA_FLUSH_SZ;
|
||||||
@ -231,7 +172,7 @@ namespace AaxDecrypter
|
|||||||
int bytesRead;
|
int bytesRead;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ);
|
bytesRead = networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ);
|
||||||
_writeFile.Write(buff, 0, bytesRead);
|
_writeFile.Write(buff, 0, bytesRead);
|
||||||
|
|
||||||
downloadPosition += bytesRead;
|
downloadPosition += bytesRead;
|
||||||
@ -242,15 +183,12 @@ namespace AaxDecrypter
|
|||||||
WritePosition = downloadPosition;
|
WritePosition = downloadPosition;
|
||||||
Update();
|
Update();
|
||||||
nextFlush = downloadPosition + DATA_FLUSH_SZ;
|
nextFlush = downloadPosition + DATA_FLUSH_SZ;
|
||||||
downloadedPiece.Set();
|
_downloadedPiece.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
} while (downloadPosition < ContentLength && !IsCancelled && bytesRead > 0);
|
} while (downloadPosition < ContentLength && !IsCancelled && bytesRead > 0);
|
||||||
|
|
||||||
_writeFile.Close();
|
|
||||||
_networkStream.Close();
|
|
||||||
WritePosition = downloadPosition;
|
WritePosition = downloadPosition;
|
||||||
Update();
|
|
||||||
|
|
||||||
if (!IsCancelled && WritePosition < ContentLength)
|
if (!IsCancelled && WritePosition < ContentLength)
|
||||||
throw new WebException($"Downloaded size (0x{WritePosition:X10}) is less than {nameof(ContentLength)} (0x{ContentLength:X10}).");
|
throw new WebException($"Downloaded size (0x{WritePosition:X10}) is less than {nameof(ContentLength)} (0x{ContentLength:X10}).");
|
||||||
@ -264,8 +202,10 @@ namespace AaxDecrypter
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
downloadedPiece.Set();
|
networkStream.Close();
|
||||||
downloadEnded.Set();
|
_writeFile.Close();
|
||||||
|
_downloadedPiece.Set();
|
||||||
|
Update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,96 +214,7 @@ namespace AaxDecrypter
|
|||||||
#region Json Connverters
|
#region Json Connverters
|
||||||
|
|
||||||
public static JsonSerializerSettings GetJsonSerializerSettings()
|
public static JsonSerializerSettings GetJsonSerializerSettings()
|
||||||
{
|
=> new JsonSerializerSettings();
|
||||||
var settings = new JsonSerializerSettings();
|
|
||||||
settings.Converters.Add(new CookieContainerConverter());
|
|
||||||
settings.Converters.Add(new WebHeaderCollectionConverter());
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class CookieContainerConverter : JsonConverter
|
|
||||||
{
|
|
||||||
public override bool CanConvert(Type objectType)
|
|
||||||
=> objectType == typeof(SingleUriCookieContainer);
|
|
||||||
|
|
||||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
|
||||||
{
|
|
||||||
var jObj = JObject.Load(reader);
|
|
||||||
|
|
||||||
var result = new SingleUriCookieContainer()
|
|
||||||
{
|
|
||||||
Uri = new Uri(jObj["Uri"].Value<string>()),
|
|
||||||
Capacity = jObj["Capacity"].Value<int>(),
|
|
||||||
MaxCookieSize = jObj["MaxCookieSize"].Value<int>(),
|
|
||||||
PerDomainCapacity = jObj["PerDomainCapacity"].Value<int>()
|
|
||||||
};
|
|
||||||
|
|
||||||
var cookieList = jObj["Cookies"].ToList();
|
|
||||||
|
|
||||||
foreach (var cookie in cookieList)
|
|
||||||
{
|
|
||||||
result.Add(
|
|
||||||
new Cookie
|
|
||||||
{
|
|
||||||
Comment = cookie["Comment"].Value<string>(),
|
|
||||||
HttpOnly = cookie["HttpOnly"].Value<bool>(),
|
|
||||||
Discard = cookie["Discard"].Value<bool>(),
|
|
||||||
Domain = cookie["Domain"].Value<string>(),
|
|
||||||
Expired = cookie["Expired"].Value<bool>(),
|
|
||||||
Expires = cookie["Expires"].Value<DateTime>(),
|
|
||||||
Name = cookie["Name"].Value<string>(),
|
|
||||||
Path = cookie["Path"].Value<string>(),
|
|
||||||
Port = cookie["Port"].Value<string>(),
|
|
||||||
Secure = cookie["Secure"].Value<bool>(),
|
|
||||||
Value = cookie["Value"].Value<string>(),
|
|
||||||
Version = cookie["Version"].Value<int>(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanWrite => true;
|
|
||||||
|
|
||||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
|
||||||
{
|
|
||||||
var cookies = value as SingleUriCookieContainer;
|
|
||||||
var obj = (JObject)JToken.FromObject(value);
|
|
||||||
var container = cookies.GetCookies();
|
|
||||||
var propertyNames = container.Select(c => JToken.FromObject(c));
|
|
||||||
obj.AddFirst(new JProperty("Cookies", new JArray(propertyNames)));
|
|
||||||
obj.WriteTo(writer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class WebHeaderCollectionConverter : JsonConverter
|
|
||||||
{
|
|
||||||
public override bool CanConvert(Type objectType)
|
|
||||||
=> objectType == typeof(WebHeaderCollection);
|
|
||||||
|
|
||||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
|
||||||
{
|
|
||||||
var jObj = JObject.Load(reader);
|
|
||||||
var result = new WebHeaderCollection();
|
|
||||||
|
|
||||||
foreach (var kvp in jObj)
|
|
||||||
result.Add(kvp.Key, kvp.Value.Value<string>());
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanWrite => true;
|
|
||||||
|
|
||||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
|
||||||
{
|
|
||||||
var jObj = new JObject();
|
|
||||||
var type = value.GetType();
|
|
||||||
var headers = value as WebHeaderCollection;
|
|
||||||
var jHeaders = headers.AllKeys.Select(k => new JProperty(k, headers[k]));
|
|
||||||
jObj.Add(jHeaders);
|
|
||||||
jObj.WriteTo(writer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -383,8 +234,7 @@ namespace AaxDecrypter
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!hasBegunDownloading)
|
_backgroundDownloadTask ??= BeginDownloading();
|
||||||
BeginDownloading();
|
|
||||||
return ContentLength;
|
return ContentLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -401,15 +251,14 @@ namespace AaxDecrypter
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public override int WriteTimeout { get => base.WriteTimeout; set => base.WriteTimeout = value; }
|
public override int WriteTimeout { get => base.WriteTimeout; set => base.WriteTimeout = value; }
|
||||||
|
|
||||||
public override void Flush() => throw new NotImplementedException();
|
public override void Flush() => throw new InvalidOperationException();
|
||||||
public override void SetLength(long value) => throw new NotImplementedException();
|
public override void SetLength(long value) => throw new InvalidOperationException();
|
||||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
|
public override void Write(byte[] buffer, int offset, int count) => throw new InvalidOperationException();
|
||||||
|
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
if (!hasBegunDownloading)
|
_backgroundDownloadTask ??= BeginDownloading();
|
||||||
BeginDownloading();
|
|
||||||
|
|
||||||
var toRead = Math.Min(count, Length - Position);
|
var toRead = Math.Min(count, Length - Position);
|
||||||
WaitToPosition(Position + toRead);
|
WaitToPosition(Position + toRead);
|
||||||
return _readFile.Read(buffer, offset, count);
|
return _readFile.Read(buffer, offset, count);
|
||||||
@ -428,38 +277,32 @@ namespace AaxDecrypter
|
|||||||
return _readFile.Position = newPosition;
|
return _readFile.Position = newPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Blocks until the file has downloaded to at least <paramref name="requiredPosition"/>, then returns. </summary>
|
||||||
/// Blocks until the file has downloaded to at least <paramref name="requiredPosition"/>, then returns.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="requiredPosition">The minimum required flished data length in <see cref="SaveFilePath"/>.</param>
|
/// <param name="requiredPosition">The minimum required flished data length in <see cref="SaveFilePath"/>.</param>
|
||||||
private void WaitToPosition(long requiredPosition)
|
private void WaitToPosition(long requiredPosition)
|
||||||
{
|
{
|
||||||
while (WritePosition < requiredPosition
|
while (WritePosition < requiredPosition
|
||||||
&& hasBegunDownloading
|
&& _backgroundDownloadTask?.IsCompleted is false
|
||||||
&& !IsCancelled
|
&& !IsCancelled)
|
||||||
&& !downloadEnded.WaitOne(0))
|
|
||||||
{
|
{
|
||||||
downloadedPiece.WaitOne(100);
|
_downloadedPiece.WaitOne(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Close()
|
public override void Close()
|
||||||
{
|
{
|
||||||
IsCancelled = true;
|
IsCancelled = true;
|
||||||
|
_backgroundDownloadTask?.Wait();
|
||||||
while (downloadEnded is not null && !downloadEnded.WaitOne(100)) ;
|
|
||||||
|
|
||||||
_readFile.Close();
|
_readFile.Close();
|
||||||
_writeFile.Close();
|
_writeFile.Close();
|
||||||
_networkStream?.Close();
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
~NetworkFileStream()
|
~NetworkFileStream()
|
||||||
{
|
{
|
||||||
downloadEnded?.Close();
|
_downloadedPiece?.Close();
|
||||||
downloadedPiece?.Close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<RecyclePool x:Key="RecyclePool" />
|
<RecyclePool x:Key="RecyclePool" />
|
||||||
<DataTemplate x:Key="queuedBook">
|
<DataTemplate x:Key="queuedBook">
|
||||||
<CheckBox Margin="10,0,0,0" Content="{Binding Item}" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
|
<CheckBox HorizontalAlignment="Stretch" Margin="10,0,0,0" Content="{Binding Item}" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<RecyclingElementFactory x:Key="elementFactory" RecyclePool="{StaticResource RecyclePool}">
|
<RecyclingElementFactory x:Key="elementFactory" RecyclePool="{StaticResource RecyclePool}">
|
||||||
<RecyclingElementFactory.Templates>
|
<RecyclingElementFactory.Templates>
|
||||||
|
|||||||
@ -55,6 +55,9 @@
|
|||||||
<Compile Update="ViewModels\MainVM.*.cs">
|
<Compile Update="ViewModels\MainVM.*.cs">
|
||||||
<DependentUpon>MainVM.cs</DependentUpon>
|
<DependentUpon>MainVM.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Update="Views\MainWindow.*.cs">
|
||||||
|
<DependentUpon>MainWindow.axaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -18,22 +18,6 @@ namespace LibationAvalonia
|
|||||||
return defaultBrush;
|
return defaultBrush;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Window GetParentWindow(this IControl control)
|
public static Window GetParentWindow(this IControl control) => control.VisualRoot as Window;
|
||||||
{
|
|
||||||
Window window = null;
|
|
||||||
|
|
||||||
var p = control.Parent;
|
|
||||||
while (p != null)
|
|
||||||
{
|
|
||||||
if (p is Window)
|
|
||||||
{
|
|
||||||
window = (Window)p;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
p = p.Parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using Dinah.Core;
|
|||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace LibationAvalonia.Controls
|
namespace LibationAvalonia.Controls
|
||||||
{
|
{
|
||||||
@ -16,7 +17,6 @@ namespace LibationAvalonia.Controls
|
|||||||
public static readonly StyledProperty<string> SubDirectoryProperty =
|
public static readonly StyledProperty<string> SubDirectoryProperty =
|
||||||
AvaloniaProperty.Register<DirectorySelectControl, string>(nameof(SubDirectory));
|
AvaloniaProperty.Register<DirectorySelectControl, string>(nameof(SubDirectory));
|
||||||
|
|
||||||
|
|
||||||
public static readonly StyledProperty<string> DirectoryProperty =
|
public static readonly StyledProperty<string> DirectoryProperty =
|
||||||
AvaloniaProperty.Register<DirectorySelectControl, string>(nameof(Directory));
|
AvaloniaProperty.Register<DirectorySelectControl, string>(nameof(Directory));
|
||||||
|
|
||||||
@ -90,8 +90,19 @@ namespace LibationAvalonia.Controls
|
|||||||
|
|
||||||
private async void CustomDirBrowseBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
private async void CustomDirBrowseBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
OpenFolderDialog ofd = new();
|
var options = new Avalonia.Platform.Storage.FolderPickerOpenOptions
|
||||||
customStates.CustomDir = await ofd.ShowAsync(VisualRoot as Window);
|
{
|
||||||
|
AllowMultiple = false
|
||||||
|
};
|
||||||
|
|
||||||
|
var selectedFolders = await (VisualRoot as Window).StorageProvider.OpenFolderPickerAsync(options);
|
||||||
|
|
||||||
|
customStates.CustomDir =
|
||||||
|
selectedFolders
|
||||||
|
.SingleOrDefault()?.
|
||||||
|
TryGetUri(out var uri) is true
|
||||||
|
? uri.LocalPath
|
||||||
|
: customStates.CustomDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckStates_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
private void CheckStates_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
@ -125,7 +136,6 @@ namespace LibationAvalonia.Controls
|
|||||||
Directory = customStates.CustomChecked ? selectedDir : System.IO.Path.Combine(selectedDir, SubDirectory);
|
Directory = customStates.CustomChecked ? selectedDir : System.IO.Path.Combine(selectedDir, SubDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void DirectoryOrCustomSelectControl_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
private void DirectoryOrCustomSelectControl_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Property.Name == nameof(Directory) && e.OldValue is null)
|
if (e.Property.Name == nameof(Directory) && e.OldValue is null)
|
||||||
|
|||||||
@ -9,6 +9,9 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using AudibleApi;
|
using AudibleApi;
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
|
using LibationFileManager;
|
||||||
|
using Avalonia.Platform.Storage.FileIO;
|
||||||
|
|
||||||
namespace LibationAvalonia.Dialogs
|
namespace LibationAvalonia.Dialogs
|
||||||
{
|
{
|
||||||
@ -110,24 +113,29 @@ namespace LibationAvalonia.Dialogs
|
|||||||
|
|
||||||
public async void ImportButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public async void ImportButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
var openFileDialogOptions = new FilePickerOpenOptions
|
||||||
OpenFileDialog ofd = new();
|
{
|
||||||
ofd.Filters.Add(new() { Name = "JSON File", Extensions = new() { "json" } });
|
Title = $"Select the audible-cli [account].json file",
|
||||||
ofd.Directory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
AllowMultiple = false,
|
||||||
ofd.AllowMultiple = false;
|
SuggestedStartLocation = new BclStorageFolder(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)),
|
||||||
|
FileTypeFilter = new FilePickerFileType[]
|
||||||
|
{
|
||||||
|
new("JSON files (*.json)") { Patterns = new[] { "json" } },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
string audibleAppDataDir = GetAudibleCliAppDataPath();
|
string audibleAppDataDir = GetAudibleCliAppDataPath();
|
||||||
|
|
||||||
if (Directory.Exists(audibleAppDataDir))
|
if (Directory.Exists(audibleAppDataDir))
|
||||||
ofd.Directory = audibleAppDataDir;
|
openFileDialogOptions.SuggestedStartLocation = new BclStorageFolder(audibleAppDataDir);
|
||||||
|
|
||||||
var filePath = await ofd.ShowAsync(this);
|
var selectedFiles = await StorageProvider.OpenFilePickerAsync(openFileDialogOptions);
|
||||||
|
var selectedFile = selectedFiles.SingleOrDefault();
|
||||||
|
|
||||||
if (filePath is null || filePath.Length == 0) return;
|
if (!selectedFile.TryGetUri(out var uri)) return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var jsonText = File.ReadAllText(filePath[0]);
|
var jsonText = File.ReadAllText(uri.LocalPath);
|
||||||
var mkbAuth = Mkb79Auth.FromJson(jsonText);
|
var mkbAuth = Mkb79Auth.FromJson(jsonText);
|
||||||
var account = await mkbAuth.ToAccountAsync();
|
var account = await mkbAuth.ToAccountAsync();
|
||||||
|
|
||||||
@ -148,7 +156,7 @@ namespace LibationAvalonia.Dialogs
|
|||||||
{
|
{
|
||||||
await MessageBox.ShowAdminAlert(
|
await MessageBox.ShowAdminAlert(
|
||||||
this,
|
this,
|
||||||
$"An error occurred while importing an account from:\r\n{filePath[0]}\r\n\r\nIs the file encrypted?",
|
$"An error occurred while importing an account from:\r\n{uri.LocalPath}\r\n\r\nIs the file encrypted?",
|
||||||
"Error Importing Account",
|
"Error Importing Account",
|
||||||
ex);
|
ex);
|
||||||
}
|
}
|
||||||
@ -263,26 +271,36 @@ namespace LibationAvalonia.Dialogs
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveFileDialog sfd = new();
|
var options = new FilePickerSaveOptions
|
||||||
sfd.Filters.Add(new() { Name = "JSON File", Extensions = new() { "json" } });
|
{
|
||||||
|
Title = $"Save Sover Image",
|
||||||
|
SuggestedStartLocation = new BclStorageFolder(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)),
|
||||||
|
SuggestedFileName = $"{acc.AccountId}.json",
|
||||||
|
DefaultExtension = "json",
|
||||||
|
ShowOverwritePrompt = true,
|
||||||
|
FileTypeChoices = new FilePickerFileType[]
|
||||||
|
{
|
||||||
|
new("JSON files (*.json)") { Patterns = new[] { "json" } },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
string audibleAppDataDir = GetAudibleCliAppDataPath();
|
string audibleAppDataDir = GetAudibleCliAppDataPath();
|
||||||
|
|
||||||
if (Directory.Exists(audibleAppDataDir))
|
if (Directory.Exists(audibleAppDataDir))
|
||||||
sfd.Directory = audibleAppDataDir;
|
options.SuggestedStartLocation = new BclStorageFolder(audibleAppDataDir);
|
||||||
|
|
||||||
string fileName = await sfd.ShowAsync(this);
|
var selectedFile = await StorageProvider.SaveFilePickerAsync(options);
|
||||||
if (fileName is null)
|
|
||||||
return;
|
if (!selectedFile.TryGetUri(out var uri)) return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var mkbAuth = Mkb79Auth.FromAccount(account);
|
var mkbAuth = Mkb79Auth.FromAccount(account);
|
||||||
var jsonText = mkbAuth.ToJson();
|
var jsonText = mkbAuth.ToJson();
|
||||||
|
|
||||||
File.WriteAllText(fileName, jsonText);
|
File.WriteAllText(uri.LocalPath, jsonText);
|
||||||
|
|
||||||
await MessageBox.Show(this, $"Successfully exported {account.AccountName} to\r\n\r\n{fileName}", "Success!");
|
await MessageBox.Show(this, $"Successfully exported {account.AccountName} to\r\n\r\n{uri.LocalPath}", "Success!");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -7,6 +7,7 @@ using System;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
|
|
||||||
namespace LibationAvalonia.Dialogs
|
namespace LibationAvalonia.Dialogs
|
||||||
{
|
{
|
||||||
@ -46,27 +47,30 @@ namespace LibationAvalonia.Dialogs
|
|||||||
|
|
||||||
public async void SaveImage_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public async void SaveImage_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
var options = new FilePickerSaveOptions
|
||||||
|
{
|
||||||
|
Title = $"Save Sover Image",
|
||||||
|
SuggestedStartLocation = new Avalonia.Platform.Storage.FileIO.BclStorageFolder(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)),
|
||||||
|
SuggestedFileName = $"{PictureFileName}.jpg",
|
||||||
|
DefaultExtension = "jpg",
|
||||||
|
ShowOverwritePrompt = true,
|
||||||
|
FileTypeChoices = new FilePickerFileType[]
|
||||||
|
{
|
||||||
|
new("Jpeg (*.jpg)") { Patterns = new[] { "jpg" } }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
SaveFileDialog saveFileDialog = new();
|
var selectedFile = await StorageProvider.SaveFilePickerAsync(options);
|
||||||
saveFileDialog.Filters.Add(new FileDialogFilter { Name = "Jpeg", Extensions = new System.Collections.Generic.List<string>() { "jpg" } });
|
|
||||||
saveFileDialog.InitialFileName = PictureFileName;
|
|
||||||
saveFileDialog.Directory
|
|
||||||
= !LibationFileManager.Configuration.IsWindows ? null
|
|
||||||
: Directory.Exists(BookSaveDirectory) ? BookSaveDirectory
|
|
||||||
: Path.GetDirectoryName(BookSaveDirectory);
|
|
||||||
|
|
||||||
var fileName = await saveFileDialog.ShowAsync(this);
|
if (!selectedFile.TryGetUri(out var uri)) return;
|
||||||
|
|
||||||
if (fileName is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File.WriteAllBytes(fileName, CoverBytes);
|
File.WriteAllBytes(uri.LocalPath, CoverBytes);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Error(ex, $"Failed to save picture to {fileName}");
|
Serilog.Log.Logger.Error(ex, $"Failed to save picture to {uri.LocalPath}");
|
||||||
await MessageBox.Show(this, $"An error was encountered while trying to save the picture\r\n\r\n{ex.Message}", "Failed to save picture", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
|
await MessageBox.Show(this, $"An error was encountered while trying to save the picture\r\n\r\n{ex.Message}", "Failed to save picture", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -351,21 +351,27 @@
|
|||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</controls:GroupBox>
|
</controls:GroupBox>
|
||||||
|
<controls:GroupBox
|
||||||
<StackPanel
|
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
|
Margin="5"
|
||||||
|
BorderWidth="1"
|
||||||
|
Label="Temporary Files Location">
|
||||||
|
|
||||||
|
<StackPanel
|
||||||
Margin="5" >
|
Margin="5" >
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="0,0,0,10"
|
Margin="0,0,0,10"
|
||||||
Text="{Binding DownloadDecryptSettings.InProgressDescriptionText}" />
|
Text="{Binding DownloadDecryptSettings.InProgressDescriptionText}" />
|
||||||
|
|
||||||
<controls:DirectorySelectControl
|
<controls:DirectoryOrCustomSelectControl
|
||||||
SubDirectory="Libation\DecryptInProgress"
|
SubDirectory="Libation"
|
||||||
SelectedDirectory="{Binding DownloadDecryptSettings.InProgressDirectory, Mode=TwoWay}" />
|
Directory="{Binding DownloadDecryptSettings.InProgressDirectory, Mode=TwoWay}"
|
||||||
|
KnownDirectories="{Binding DownloadDecryptSettings.KnownDirectories}" />
|
||||||
|
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
</controls:GroupBox>
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
|
|||||||
@ -9,6 +9,7 @@ using ReactiveUI;
|
|||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FileManager;
|
using FileManager;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace LibationAvalonia.Dialogs
|
namespace LibationAvalonia.Dialogs
|
||||||
{
|
{
|
||||||
@ -227,7 +228,6 @@ namespace LibationAvalonia.Dialogs
|
|||||||
|
|
||||||
public class DownloadDecryptSettings : ViewModels.ViewModelBase, ISettingsDisplay
|
public class DownloadDecryptSettings : ViewModels.ViewModelBase, ISettingsDisplay
|
||||||
{
|
{
|
||||||
|
|
||||||
private bool _badBookAsk;
|
private bool _badBookAsk;
|
||||||
private bool _badBookAbort;
|
private bool _badBookAbort;
|
||||||
private bool _badBookRetry;
|
private bool _badBookRetry;
|
||||||
@ -242,7 +242,16 @@ namespace LibationAvalonia.Dialogs
|
|||||||
LoadSettings(config);
|
LoadSettings(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Configuration.KnownDirectories InProgressDirectory { get; set; }
|
public List<Configuration.KnownDirectories> KnownDirectories { get; } = new()
|
||||||
|
{
|
||||||
|
Configuration.KnownDirectories.WinTemp,
|
||||||
|
Configuration.KnownDirectories.UserProfile,
|
||||||
|
Configuration.KnownDirectories.AppDir,
|
||||||
|
Configuration.KnownDirectories.MyDocs,
|
||||||
|
Configuration.KnownDirectories.LibationFiles
|
||||||
|
};
|
||||||
|
|
||||||
|
public string InProgressDirectory { get; set; }
|
||||||
public void LoadSettings(Configuration config)
|
public void LoadSettings(Configuration config)
|
||||||
{
|
{
|
||||||
BadBookAsk = config.BadBook is Configuration.BadBookAction.Ask;
|
BadBookAsk = config.BadBook is Configuration.BadBookAction.Ask;
|
||||||
@ -252,9 +261,7 @@ namespace LibationAvalonia.Dialogs
|
|||||||
FolderTemplate = config.FolderTemplate;
|
FolderTemplate = config.FolderTemplate;
|
||||||
FileTemplate = config.FileTemplate;
|
FileTemplate = config.FileTemplate;
|
||||||
ChapterFileTemplate = config.ChapterFileTemplate;
|
ChapterFileTemplate = config.ChapterFileTemplate;
|
||||||
InProgressDirectory
|
InProgressDirectory = config.InProgress;
|
||||||
= config.InProgress == Configuration.AppDir_Absolute ? Configuration.KnownDirectories.AppDir
|
|
||||||
: Configuration.GetKnownDirectory(config.InProgress);
|
|
||||||
UseCoverAsFolderIcon = config.UseCoverAsFolderIcon;
|
UseCoverAsFolderIcon = config.UseCoverAsFolderIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,9 +296,7 @@ namespace LibationAvalonia.Dialogs
|
|||||||
config.FolderTemplate = FolderTemplate;
|
config.FolderTemplate = FolderTemplate;
|
||||||
config.FileTemplate = FileTemplate;
|
config.FileTemplate = FileTemplate;
|
||||||
config.ChapterFileTemplate = ChapterFileTemplate;
|
config.ChapterFileTemplate = ChapterFileTemplate;
|
||||||
config.InProgress
|
config.InProgress = InProgressDirectory;
|
||||||
= InProgressDirectory is Configuration.KnownDirectories.AppDir ? Configuration.AppDir_Absolute
|
|
||||||
: Configuration.GetKnownDirectoryPath(InProgressDirectory);
|
|
||||||
|
|
||||||
config.UseCoverAsFolderIcon = UseCoverAsFolderIcon;
|
config.UseCoverAsFolderIcon = UseCoverAsFolderIcon;
|
||||||
|
|
||||||
|
|||||||
@ -18,95 +18,98 @@
|
|||||||
<StartupObject />
|
<StartupObject />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!--
|
<!--
|
||||||
HACK FOR COMPILER BUG 2021-09-14. Hopefully will be fixed in future versions
|
HACK FOR COMPILER BUG 2021-09-14. Hopefully will be fixed in future versions
|
||||||
- Not using SatelliteResourceLanguages will load all language packs: works
|
- Not using SatelliteResourceLanguages will load all language packs: works
|
||||||
- Specifying 'en' semicolon 1 more should load 1 language pack: works
|
- Specifying 'en' semicolon 1 more should load 1 language pack: works
|
||||||
- Specifying only 'en' should load no language packs: broken, still loads all
|
- Specifying only 'en' should load no language packs: broken, still loads all
|
||||||
-->
|
-->
|
||||||
<SatelliteResourceLanguages>en;es</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>en;es</SatelliteResourceLanguages>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<OutputPath>..\bin\Avalonia\Debug</OutputPath>
|
<OutputPath>..\bin\Avalonia\Debug</OutputPath>
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
<OutputPath>..\bin\Avalonia\Release</OutputPath>
|
<OutputPath>..\bin\Avalonia\Release</OutputPath>
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<AvaloniaResource Include="Assets\**" />
|
|
||||||
<None Remove=".gitignore" />
|
|
||||||
<None Remove="Assets\Asterisk.png" />
|
|
||||||
<None Remove="Assets\cancel.png" />
|
|
||||||
<None Remove="Assets\completed.png" />
|
|
||||||
<None Remove="Assets\down.png" />
|
|
||||||
<None Remove="Assets\download-arrow.png" />
|
|
||||||
<None Remove="Assets\edit-tags-25x25.png" />
|
|
||||||
<None Remove="Assets\edit-tags-50x50.png" />
|
|
||||||
<None Remove="Assets\edit_25x25.png" />
|
|
||||||
<None Remove="Assets\edit_64x64.png" />
|
|
||||||
<None Remove="Assets\error.png" />
|
|
||||||
<None Remove="Assets\errored.png" />
|
|
||||||
<None Remove="Assets\Exclamation.png" />
|
|
||||||
<None Remove="Assets\first.png" />
|
|
||||||
<None Remove="Assets\glass-with-glow_16.png" />
|
|
||||||
<None Remove="Assets\img-coverart-prod-unavailable_300x300.jpg" />
|
|
||||||
<None Remove="Assets\img-coverart-prod-unavailable_500x500.jpg" />
|
|
||||||
<None Remove="Assets\img-coverart-prod-unavailable_80x80.jpg" />
|
|
||||||
<None Remove="Assets\import_16x16.png" />
|
|
||||||
<None Remove="Assets\last.png" />
|
|
||||||
<None Remove="Assets\libation.ico" />
|
|
||||||
<None Remove="Assets\LibationStyles.xaml" />
|
|
||||||
<None Remove="Assets\liberate_green.png" />
|
|
||||||
<None Remove="Assets\liberate_green_pdf_no.png" />
|
|
||||||
<None Remove="Assets\liberate_green_pdf_yes.png" />
|
|
||||||
<None Remove="Assets\liberate_red.png" />
|
|
||||||
<None Remove="Assets\liberate_red_pdf_no.png" />
|
|
||||||
<None Remove="Assets\liberate_red_pdf_yes.png" />
|
|
||||||
<None Remove="Assets\liberate_yellow.png" />
|
|
||||||
<None Remove="Assets\liberate_yellow_pdf_no.png" />
|
|
||||||
<None Remove="Assets\liberate_yellow_pdf_yes.png" />
|
|
||||||
<None Remove="Assets\MBIcons\Asterisk.png" />
|
|
||||||
<None Remove="Assets\MBIcons\error.png" />
|
|
||||||
<None Remove="Assets\MBIcons\Exclamation.png" />
|
|
||||||
<None Remove="Assets\MBIcons\Question.png" />
|
|
||||||
<None Remove="Assets\minus.png" />
|
|
||||||
<None Remove="Assets\plus.png" />
|
|
||||||
<None Remove="Assets\Question.png" />
|
|
||||||
<None Remove="Assets\queued.png" />
|
|
||||||
<None Remove="Assets\up.png" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
<ProjectReference Include="..\AppScaffolding\AppScaffolding.csproj" />
|
<None Remove=".gitignore" />
|
||||||
<ProjectReference Include="..\FileLiberator\FileLiberator.csproj" />
|
<None Remove="Assets\Asterisk.png" />
|
||||||
</ItemGroup>
|
<None Remove="Assets\cancel.png" />
|
||||||
|
<None Remove="Assets\completed.png" />
|
||||||
|
<None Remove="Assets\down.png" />
|
||||||
|
<None Remove="Assets\download-arrow.png" />
|
||||||
|
<None Remove="Assets\edit-tags-25x25.png" />
|
||||||
|
<None Remove="Assets\edit-tags-50x50.png" />
|
||||||
|
<None Remove="Assets\edit_25x25.png" />
|
||||||
|
<None Remove="Assets\edit_64x64.png" />
|
||||||
|
<None Remove="Assets\error.png" />
|
||||||
|
<None Remove="Assets\errored.png" />
|
||||||
|
<None Remove="Assets\Exclamation.png" />
|
||||||
|
<None Remove="Assets\first.png" />
|
||||||
|
<None Remove="Assets\glass-with-glow_16.png" />
|
||||||
|
<None Remove="Assets\img-coverart-prod-unavailable_300x300.jpg" />
|
||||||
|
<None Remove="Assets\img-coverart-prod-unavailable_500x500.jpg" />
|
||||||
|
<None Remove="Assets\img-coverart-prod-unavailable_80x80.jpg" />
|
||||||
|
<None Remove="Assets\import_16x16.png" />
|
||||||
|
<None Remove="Assets\last.png" />
|
||||||
|
<None Remove="Assets\libation.ico" />
|
||||||
|
<None Remove="Assets\LibationStyles.xaml" />
|
||||||
|
<None Remove="Assets\liberate_green.png" />
|
||||||
|
<None Remove="Assets\liberate_green_pdf_no.png" />
|
||||||
|
<None Remove="Assets\liberate_green_pdf_yes.png" />
|
||||||
|
<None Remove="Assets\liberate_red.png" />
|
||||||
|
<None Remove="Assets\liberate_red_pdf_no.png" />
|
||||||
|
<None Remove="Assets\liberate_red_pdf_yes.png" />
|
||||||
|
<None Remove="Assets\liberate_yellow.png" />
|
||||||
|
<None Remove="Assets\liberate_yellow_pdf_no.png" />
|
||||||
|
<None Remove="Assets\liberate_yellow_pdf_yes.png" />
|
||||||
|
<None Remove="Assets\MBIcons\Asterisk.png" />
|
||||||
|
<None Remove="Assets\MBIcons\error.png" />
|
||||||
|
<None Remove="Assets\MBIcons\Exclamation.png" />
|
||||||
|
<None Remove="Assets\MBIcons\Question.png" />
|
||||||
|
<None Remove="Assets\minus.png" />
|
||||||
|
<None Remove="Assets\plus.png" />
|
||||||
|
<None Remove="Assets\Question.png" />
|
||||||
|
<None Remove="Assets\queued.png" />
|
||||||
|
<None Remove="Assets\up.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Properties\Resources.Designer.cs">
|
<ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" />
|
||||||
<DesignTime>True</DesignTime>
|
<ProjectReference Include="..\AppScaffolding\AppScaffolding.csproj" />
|
||||||
<AutoGen>True</AutoGen>
|
<ProjectReference Include="..\FileLiberator\FileLiberator.csproj" />
|
||||||
<DependentUpon>Resources.resx</DependentUpon>
|
</ItemGroup>
|
||||||
</Compile>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Update="Properties\Resources.resx">
|
<Compile Update="Properties\Resources.Designer.cs">
|
||||||
<Generator>ResXFileCodeGenerator</Generator>
|
<DesignTime>True</DesignTime>
|
||||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
<AutoGen>True</AutoGen>
|
||||||
</EmbeddedResource>
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
</ItemGroup>
|
</Compile>
|
||||||
|
<Compile Update="Views\MainWindow.*.cs">
|
||||||
|
<DependentUpon>MainWindow.axaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Update="Properties\Resources.resx">
|
||||||
|
<Generator>ResXFileCodeGenerator</Generator>
|
||||||
|
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||||
|
</EmbeddedResource>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<UpToDateCheckInput Remove="Controls\GroupBox.axaml" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<UpToDateCheckInput Remove="Controls\GroupBox.axaml" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="11.0.0-preview4" />
|
<PackageReference Include="Avalonia" Version="11.0.0-preview4" />
|
||||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.0-preview4" />
|
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.0-preview4" />
|
||||||
@ -120,16 +123,16 @@
|
|||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="glass-with-glow_256.svg">
|
<None Update="glass-with-glow_256.svg">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Libation.desktop">
|
<None Update="Libation.desktop">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="ZipExtractor.exe">
|
<None Update="ZipExtractor.exe">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
<Target Name="SpicNSpan" AfterTargets="Clean">
|
<Target Name="SpicNSpan" AfterTargets="Clean">
|
||||||
|
|||||||
62
Source/LibationAvalonia/Views/MainWindow.Export.cs
Normal file
62
Source/LibationAvalonia/Views/MainWindow.Export.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using ApplicationServices;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
|
using LibationFileManager;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace LibationAvalonia.Views
|
||||||
|
{
|
||||||
|
//DONE
|
||||||
|
public partial class MainWindow
|
||||||
|
{
|
||||||
|
private void Configure_Export() { }
|
||||||
|
|
||||||
|
public async void exportLibraryToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var options = new FilePickerSaveOptions
|
||||||
|
{
|
||||||
|
Title = "Where to export Library",
|
||||||
|
SuggestedStartLocation = new Avalonia.Platform.Storage.FileIO.BclStorageFolder(Configuration.Instance.Books),
|
||||||
|
SuggestedFileName = $"Libation Library Export {DateTime.Now:yyyy-MM-dd}.xlsx",
|
||||||
|
DefaultExtension = "xlsx",
|
||||||
|
ShowOverwritePrompt = true,
|
||||||
|
FileTypeChoices = new FilePickerFileType[]
|
||||||
|
{
|
||||||
|
new("Excel Workbook (*.xlsx)") { Patterns = new[] { "xlsx" } },
|
||||||
|
new("CSV files (*.csv)") { Patterns = new[] { "csv" } },
|
||||||
|
new("JSON files (*.json)") { Patterns = new[] { "json" } },
|
||||||
|
new("All files (*.*)") { Patterns = new[] { "*" } },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var selectedFile = await StorageProvider.SaveFilePickerAsync(options);
|
||||||
|
|
||||||
|
if (!selectedFile.TryGetUri(out var uri)) return;
|
||||||
|
|
||||||
|
var ext = System.IO.Path.GetExtension(uri.LocalPath);
|
||||||
|
switch (ext)
|
||||||
|
{
|
||||||
|
case "xlsx": // xlsx
|
||||||
|
default:
|
||||||
|
LibraryExporter.ToXlsx(uri.LocalPath);
|
||||||
|
break;
|
||||||
|
case "csv": // csv
|
||||||
|
LibraryExporter.ToCsv(uri.LocalPath);
|
||||||
|
break;
|
||||||
|
case "json": // json
|
||||||
|
LibraryExporter.ToJson(uri.LocalPath);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await MessageBox.Show("Library exported to:\r\n" + uri.LocalPath, "Library Exported");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await MessageBox.ShowAdminAlert(this, "Error attempting to export your library.", "Error exporting", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,52 +0,0 @@
|
|||||||
using ApplicationServices;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace LibationAvalonia.Views
|
|
||||||
{
|
|
||||||
//DONE
|
|
||||||
public partial class MainWindow
|
|
||||||
{
|
|
||||||
private void Configure_Export() { }
|
|
||||||
|
|
||||||
public async void exportLibraryToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var saveFileDialog = new SaveFileDialog
|
|
||||||
{
|
|
||||||
Title = "Where to export Library",
|
|
||||||
};
|
|
||||||
saveFileDialog.Filters.Add(new FileDialogFilter { Name = "Excel Workbook (*.xlsx)", Extensions = new() { "xlsx" } });
|
|
||||||
saveFileDialog.Filters.Add(new FileDialogFilter { Name = "CSV files (*.csv)", Extensions = new() { "csv" } });
|
|
||||||
saveFileDialog.Filters.Add(new FileDialogFilter { Name = "JSON files (*.json)", Extensions = new() { "json" } });
|
|
||||||
saveFileDialog.Filters.Add(new FileDialogFilter { Name = "All files (*.*)", Extensions = new() { "*" } });
|
|
||||||
|
|
||||||
var fileName = await saveFileDialog.ShowAsync(this);
|
|
||||||
if (fileName is null) return;
|
|
||||||
|
|
||||||
var ext = System.IO.Path.GetExtension(fileName);
|
|
||||||
switch (ext)
|
|
||||||
{
|
|
||||||
case "xlsx": // xlsx
|
|
||||||
default:
|
|
||||||
LibraryExporter.ToXlsx(fileName);
|
|
||||||
break;
|
|
||||||
case "csv": // csv
|
|
||||||
LibraryExporter.ToCsv(fileName);
|
|
||||||
break;
|
|
||||||
case "json": // json
|
|
||||||
LibraryExporter.ToJson(fileName);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await MessageBox.Show("Library exported to:\r\n" + fileName, "Library Exported");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await MessageBox.ShowAdminAlert(this, "Error attempting to export your library.", "Error exporting", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -6,6 +6,7 @@ using Avalonia;
|
|||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
using FileLiberator;
|
using FileLiberator;
|
||||||
using LibationAvalonia.Controls;
|
using LibationAvalonia.Controls;
|
||||||
@ -42,7 +43,7 @@ namespace LibationAvalonia.Views
|
|||||||
};
|
};
|
||||||
|
|
||||||
var pdvm = new ProductsDisplayViewModel();
|
var pdvm = new ProductsDisplayViewModel();
|
||||||
pdvm.DisplayBooksAsync(sampleEntries);
|
_ = pdvm.DisplayBooksAsync(sampleEntries);
|
||||||
DataContext = pdvm;
|
DataContext = pdvm;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -106,17 +107,22 @@ namespace LibationAvalonia.Views
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var openFileDialog = new OpenFileDialog
|
var openFileDialogOptions = new FilePickerOpenOptions
|
||||||
{
|
{
|
||||||
Title = $"Locate the audio file for '{entry.Book.Title}'",
|
Title = $"Locate the audio file for '{entry.Book.Title}'",
|
||||||
Filters = new() { new() { Name = "All files (*.*)", Extensions = new() { "*" } } },
|
AllowMultiple = false,
|
||||||
AllowMultiple = false
|
SuggestedStartLocation = new Avalonia.Platform.Storage.FileIO.BclStorageFolder(Configuration.Instance.Books),
|
||||||
|
FileTypeFilter = new FilePickerFileType[]
|
||||||
|
{
|
||||||
|
new("All files (*.*)") { Patterns = new[] { "*" } },
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var filePaths = await openFileDialog.ShowAsync(this.GetParentWindow());
|
|
||||||
var filePath = filePaths.SingleOrDefault();
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(filePath))
|
var selectedFiles = await this.GetParentWindow().StorageProvider.OpenFilePickerAsync(openFileDialogOptions);
|
||||||
FilePathCache.Insert(entry.AudibleProductId, filePath);
|
var selectedFile = selectedFiles.SingleOrDefault();
|
||||||
|
|
||||||
|
if (selectedFile.TryGetUri(out var uri))
|
||||||
|
FilePathCache.Insert(entry.AudibleProductId, uri.LocalPath);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -16,7 +16,16 @@ namespace LibationFileManager
|
|||||||
public static LongPath DownloadsInProgressDirectory => Directory.CreateDirectory(Path.Combine(Configuration.Instance.InProgress, "DownloadsInProgress")).FullName;
|
public static LongPath DownloadsInProgressDirectory => Directory.CreateDirectory(Path.Combine(Configuration.Instance.InProgress, "DownloadsInProgress")).FullName;
|
||||||
public static LongPath DecryptInProgressDirectory => Directory.CreateDirectory(Path.Combine(Configuration.Instance.InProgress, "DecryptInProgress")).FullName;
|
public static LongPath DecryptInProgressDirectory => Directory.CreateDirectory(Path.Combine(Configuration.Instance.InProgress, "DecryptInProgress")).FullName;
|
||||||
|
|
||||||
private static AaxcFileStorage AAXC { get; } = new AaxcFileStorage();
|
static AudibleFileStorage()
|
||||||
|
{
|
||||||
|
//Clean up any partially-decrypted files from previous Libation instances.
|
||||||
|
//Do no clean DownloadsInProgressDirectory because those files are resumable
|
||||||
|
foreach (var tempFile in Directory.EnumerateFiles(DecryptInProgressDirectory))
|
||||||
|
FileUtility.SaferDelete(tempFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static AaxcFileStorage AAXC { get; } = new AaxcFileStorage();
|
||||||
public static bool AaxcExists(string productId) => AAXC.Exists(productId);
|
public static bool AaxcExists(string productId) => AAXC.Exists(productId);
|
||||||
|
|
||||||
public static AudioFileStorage Audio { get; } = new AudioFileStorage();
|
public static AudioFileStorage Audio { get; } = new AudioFileStorage();
|
||||||
|
|||||||
@ -25,7 +25,7 @@ namespace LibationFileManager
|
|||||||
[Description("The same folder that Libation is running from")]
|
[Description("The same folder that Libation is running from")]
|
||||||
AppDir = 2,
|
AppDir = 2,
|
||||||
|
|
||||||
[Description("Windows temporary folder")]
|
[Description("System temporary folder")]
|
||||||
WinTemp = 3,
|
WinTemp = 3,
|
||||||
|
|
||||||
[Description("My Documents")]
|
[Description("My Documents")]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user