mirror of
https://github.com/rmcrackan/Libation.git
synced 2025-12-24 06:28:02 -05:00
Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
389761355d | ||
|
|
69054afaa0 | ||
|
|
aacdcea1e1 | ||
|
|
0beb3bf437 | ||
|
|
e925b57f7f | ||
|
|
5deaa06d78 | ||
|
|
eda62975ba | ||
|
|
d91e02db29 | ||
|
|
cd604d03b1 | ||
|
|
d5cd569319 | ||
|
|
a58f51a8ce | ||
|
|
d24c10ddf5 | ||
|
|
a12391f0ab | ||
|
|
60f1d8117d | ||
|
|
20b6f28cb5 | ||
|
|
9a1fa89f6f | ||
|
|
2a294f4f85 | ||
|
|
0938c84929 | ||
|
|
99cc6a6425 | ||
|
|
0025825d5c | ||
|
|
81b6833118 | ||
|
|
a51e76d44d | ||
|
|
755a7338e9 | ||
|
|
56732a5365 | ||
|
|
dd3b032b21 | ||
|
|
dd25792864 | ||
|
|
6979ab4450 | ||
|
|
4b31207f91 | ||
|
|
84a847a838 | ||
|
|
6900a68b9d | ||
|
|
743644c4e9 | ||
|
|
e9e380dbe6 | ||
|
|
515dfceb73 | ||
|
|
3941906d72 | ||
|
|
6407d15fe0 | ||
|
|
be84fb317e | ||
|
|
3af010c1f5 | ||
|
|
714bb2ba50 | ||
|
|
2e5360f0ba | ||
|
|
258775ff3f | ||
|
|
82318ffab7 | ||
|
|
901572e7bb | ||
|
|
cfa938360a | ||
|
|
80017ce9fd | ||
|
|
c67972a327 | ||
|
|
57ee150d3c | ||
|
|
57302e1b5c | ||
|
|
09dbc67914 | ||
|
|
b768362eae | ||
|
|
04a32533cb | ||
|
|
1ad2135a3f | ||
|
|
643ae09b2b | ||
|
|
8391e43b03 | ||
|
|
8a54eda4a0 | ||
|
|
e0406378cb | ||
|
|
e1299331cc | ||
|
|
248b336867 | ||
|
|
b7d96ae447 | ||
|
|
8ab2af1c5d | ||
|
|
2d459bb2cf | ||
|
|
aeb0d2a82b | ||
|
|
f50dab94a4 | ||
|
|
efa5cefa23 | ||
|
|
2e4a97fde7 | ||
|
|
2f241806fa | ||
|
|
e417f60a36 | ||
|
|
b00f2bd908 | ||
|
|
220cda42e7 | ||
|
|
f992a7ec64 | ||
|
|
c54c45df33 | ||
|
|
a8b9e187e6 | ||
|
|
53f252e56f | ||
|
|
2827bc8904 | ||
|
|
98a775fc5a | ||
|
|
f28a729d36 | ||
|
|
00a6a4bf50 | ||
|
|
fdefa7c3bf | ||
|
|
244862299f | ||
|
|
4decf9d3b7 | ||
|
|
83f538d304 | ||
|
|
9e0e06e436 |
@@ -276,13 +276,13 @@ namespace AaxDecrypter
|
||||
};
|
||||
info.EnvironmentVariables["VARIABLE"] = decryptKey;
|
||||
|
||||
var (output, exitCode) = info.RunHidden();
|
||||
var result = info.RunHidden();
|
||||
|
||||
// bad checksum -- bad decrypt key
|
||||
if (output.Contains("checksums mismatch, aborting!"))
|
||||
if (result.Output.Contains("checksums mismatch, aborting!"))
|
||||
return -99;
|
||||
|
||||
return exitCode;
|
||||
return result.ExitCode;
|
||||
}
|
||||
|
||||
// temp file names for steps 3, 4, 5
|
||||
|
||||
@@ -21,8 +21,7 @@ namespace AaxDecrypter
|
||||
};
|
||||
|
||||
// checksum is in the debug info. ffprobe's debug info is written to stderr, not stdout
|
||||
var readErrorOutput = true;
|
||||
var ffprobeStderr = info.RunHidden(readErrorOutput).Output;
|
||||
var ffprobeStderr = info.RunHidden().Error;
|
||||
|
||||
// example checksum line:
|
||||
// ... [aax] file checksum == 0c527840c4f18517157eb0b4f9d6f9317ce60cd1
|
||||
|
||||
@@ -1,41 +1,90 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AudibleApi;
|
||||
using DataLayer;
|
||||
using DtoImporterService;
|
||||
using InternalUtilities;
|
||||
using Serilog;
|
||||
|
||||
namespace ApplicationServices
|
||||
{
|
||||
public static class LibraryCommands
|
||||
{
|
||||
public static async Task<(int totalCount, int newCount)> ImportLibraryAsync(ILoginCallback callback)
|
||||
public static async Task<(int totalCount, int newCount)> ImportAccountAsync(Func<Account, ILoginCallback> loginCallbackFactoryFunc, params Account[] accounts)
|
||||
{
|
||||
if (accounts is null || accounts.Length == 0)
|
||||
return (0, 0);
|
||||
|
||||
try
|
||||
{
|
||||
var audibleApiActions = new AudibleApiActions();
|
||||
var items = await audibleApiActions.GetAllLibraryItemsAsync(callback);
|
||||
var totalCount = items.Count;
|
||||
Serilog.Log.Logger.Information($"GetAllLibraryItems: Total count {totalCount}");
|
||||
var importItems = await scanAccountsAsync(loginCallbackFactoryFunc, accounts);
|
||||
|
||||
using var context = DbContexts.GetContext();
|
||||
var libImporter = new LibraryImporter(context);
|
||||
var newCount = await Task.Run(() => libImporter.Import(items));
|
||||
context.SaveChanges();
|
||||
Serilog.Log.Logger.Information($"Import: New count {newCount}");
|
||||
var totalCount = importItems.Count;
|
||||
Log.Logger.Information($"GetAllLibraryItems: Total count {totalCount}");
|
||||
|
||||
var newCount = await getNewCountAsync(importItems);
|
||||
Log.Logger.Information($"Import: New count {newCount}");
|
||||
|
||||
await Task.Run(() => SearchEngineCommands.FullReIndex());
|
||||
Serilog.Log.Logger.Information("FullReIndex: success");
|
||||
Log.Logger.Information("FullReIndex: success");
|
||||
|
||||
return (totalCount, newCount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "Error importing library");
|
||||
Log.Logger.Error(ex, "Error importing library");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<List<ImportItem>> scanAccountsAsync(Func<Account, ILoginCallback> loginCallbackFactoryFunc, Account[] accounts)
|
||||
{
|
||||
var tasks = new List<Task<List<ImportItem>>>();
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
var callback = loginCallbackFactoryFunc(account);
|
||||
|
||||
// get APIs in serial, esp b/c of logins
|
||||
var api = await AudibleApiActions.GetApiAsync(callback, account);
|
||||
|
||||
// add scanAccountAsync as a TASK: do not await
|
||||
tasks.Add(scanAccountAsync(api, account));
|
||||
}
|
||||
|
||||
// import library in parallel
|
||||
var arrayOfLists = await Task.WhenAll(tasks);
|
||||
var importItems = arrayOfLists.SelectMany(a => a).ToList();
|
||||
return importItems;
|
||||
}
|
||||
|
||||
private static async Task<List<ImportItem>> scanAccountAsync(Api api, Account account)
|
||||
{
|
||||
Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
|
||||
|
||||
var localeName = account.Locale?.Name;
|
||||
Log.Logger.Information("ImportLibraryAsync. {@DebugInfo}", new
|
||||
{
|
||||
account.AccountName,
|
||||
account.AccountId,
|
||||
LocaleName = localeName,
|
||||
});
|
||||
|
||||
var dtoItems = await AudibleApiActions.GetLibraryValidatedAsync(api);
|
||||
return dtoItems.Select(d => new ImportItem { DtoItem = d, AccountId = account.AccountId, LocaleName = localeName }).ToList();
|
||||
}
|
||||
|
||||
private static async Task<int> getNewCountAsync(List<ImportItem> importItems)
|
||||
{
|
||||
using var context = DbContexts.GetContext();
|
||||
var libraryImporter = new LibraryImporter(context);
|
||||
var newCount = await Task.Run(() => libraryImporter.Import(importItems));
|
||||
context.SaveChanges();
|
||||
|
||||
return newCount;
|
||||
}
|
||||
|
||||
public static int UpdateTags(this LibationContext context, Book book, string newTags)
|
||||
{
|
||||
try
|
||||
@@ -51,7 +100,7 @@ namespace ApplicationServices
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "Error updating tags");
|
||||
Log.Logger.Error(ex, "Error updating tags");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.0">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
338
DataLayer/Migrations/20200812152646_AddLocaleAndAccount.Designer.cs
generated
Normal file
338
DataLayer/Migrations/20200812152646_AddLocaleAndAccount.Designer.cs
generated
Normal file
@@ -0,0 +1,338 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using DataLayer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace DataLayer.Migrations
|
||||
{
|
||||
[DbContext(typeof(LibationContext))]
|
||||
[Migration("20200812152646_AddLocaleAndAccount")]
|
||||
partial class AddLocaleAndAccount
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "3.1.7");
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleProductId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("CategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DatePublished")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsAbridged")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("LengthInMinutes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Locale")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PictureId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("BookId");
|
||||
|
||||
b.HasIndex("AudibleProductId");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.ToTable("Books");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookContributor", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ContributorId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte>("Order")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("BookId", "ContributorId", "Role");
|
||||
|
||||
b.HasIndex("BookId");
|
||||
|
||||
b.HasIndex("ContributorId");
|
||||
|
||||
b.ToTable("BookContributor");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Category", b =>
|
||||
{
|
||||
b.Property<int>("CategoryId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleCategoryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("ParentCategoryCategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("CategoryId");
|
||||
|
||||
b.HasIndex("AudibleCategoryId");
|
||||
|
||||
b.HasIndex("ParentCategoryCategoryId");
|
||||
|
||||
b.ToTable("Categories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
CategoryId = -1,
|
||||
AudibleCategoryId = "",
|
||||
Name = ""
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Contributor", b =>
|
||||
{
|
||||
b.Property<int>("ContributorId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleContributorId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("ContributorId");
|
||||
|
||||
b.HasIndex("Name");
|
||||
|
||||
b.ToTable("Contributors");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
ContributorId = -1,
|
||||
Name = ""
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.LibraryBook", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Account")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("BookId");
|
||||
|
||||
b.ToTable("Library");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Series", b =>
|
||||
{
|
||||
b.Property<int>("SeriesId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleSeriesId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("SeriesId");
|
||||
|
||||
b.HasIndex("AudibleSeriesId");
|
||||
|
||||
b.ToTable("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.SeriesBook", b =>
|
||||
{
|
||||
b.Property<int>("SeriesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<float?>("Index")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.HasKey("SeriesId", "BookId");
|
||||
|
||||
b.HasIndex("BookId");
|
||||
|
||||
b.HasIndex("SeriesId");
|
||||
|
||||
b.ToTable("SeriesBook");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Category", "Category")
|
||||
.WithMany()
|
||||
.HasForeignKey("CategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsOne("DataLayer.Rating", "Rating", b1 =>
|
||||
{
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<float>("OverallRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.Property<float>("PerformanceRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.Property<float>("StoryRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.HasKey("BookId");
|
||||
|
||||
b1.ToTable("Books");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("BookId");
|
||||
});
|
||||
|
||||
b.OwnsMany("DataLayer.Supplement", "Supplements", b1 =>
|
||||
{
|
||||
b1.Property<int>("SupplementId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<string>("Url")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.HasKey("SupplementId");
|
||||
|
||||
b1.HasIndex("BookId");
|
||||
|
||||
b1.ToTable("Supplement");
|
||||
|
||||
b1.WithOwner("Book")
|
||||
.HasForeignKey("BookId");
|
||||
});
|
||||
|
||||
b.OwnsOne("DataLayer.UserDefinedItem", "UserDefinedItem", b1 =>
|
||||
{
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<string>("Tags")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.HasKey("BookId");
|
||||
|
||||
b1.ToTable("UserDefinedItem");
|
||||
|
||||
b1.WithOwner("Book")
|
||||
.HasForeignKey("BookId");
|
||||
|
||||
b1.OwnsOne("DataLayer.Rating", "Rating", b2 =>
|
||||
{
|
||||
b2.Property<int>("UserDefinedItemBookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b2.Property<float>("OverallRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.Property<float>("PerformanceRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.Property<float>("StoryRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.HasKey("UserDefinedItemBookId");
|
||||
|
||||
b2.ToTable("UserDefinedItem");
|
||||
|
||||
b2.WithOwner()
|
||||
.HasForeignKey("UserDefinedItemBookId");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookContributor", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithMany("ContributorsLink")
|
||||
.HasForeignKey("BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.Contributor", "Contributor")
|
||||
.WithMany("BooksLink")
|
||||
.HasForeignKey("ContributorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Category", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Category", "ParentCategory")
|
||||
.WithMany()
|
||||
.HasForeignKey("ParentCategoryCategoryId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.LibraryBook", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithOne()
|
||||
.HasForeignKey("DataLayer.LibraryBook", "BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.SeriesBook", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithMany("SeriesLink")
|
||||
.HasForeignKey("BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.Series", "Series")
|
||||
.WithMany("BooksLink")
|
||||
.HasForeignKey("SeriesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
31
DataLayer/Migrations/20200812152646_AddLocaleAndAccount.cs
Normal file
31
DataLayer/Migrations/20200812152646_AddLocaleAndAccount.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace DataLayer.Migrations
|
||||
{
|
||||
public partial class AddLocaleAndAccount : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Account",
|
||||
table: "Library",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Locale",
|
||||
table: "Books",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Account",
|
||||
table: "Library");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Locale",
|
||||
table: "Books");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace DataLayer.Migrations
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "3.0.0");
|
||||
.HasAnnotation("ProductVersion", "3.1.7");
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
@@ -40,6 +40,9 @@ namespace DataLayer.Migrations
|
||||
b.Property<int>("LengthInMinutes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Locale")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PictureId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@@ -141,6 +144,9 @@ namespace DataLayer.Migrations
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Account")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
||||
@@ -26,6 +26,9 @@ namespace DataLayer
|
||||
public string Description { get; private set; }
|
||||
public int LengthInMinutes { get; private set; }
|
||||
|
||||
// immutable-ish. should be immutable. mutability is necessary for v3 => v4 upgrades
|
||||
public string Locale { get; private set; }
|
||||
|
||||
// mutable
|
||||
public string PictureId { get; set; }
|
||||
|
||||
@@ -63,7 +66,7 @@ namespace DataLayer
|
||||
int lengthInMinutes,
|
||||
IEnumerable<Contributor> authors,
|
||||
IEnumerable<Contributor> narrators,
|
||||
Category category)
|
||||
Category category, string localeName)
|
||||
{
|
||||
// validate
|
||||
ArgumentValidator.EnsureNotNull(audibleProductId, nameof(audibleProductId));
|
||||
@@ -72,6 +75,7 @@ namespace DataLayer
|
||||
|
||||
// assign as soon as possible. stuff below relies on this
|
||||
AudibleProductId = productId;
|
||||
Locale = localeName;
|
||||
|
||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(title, nameof(title));
|
||||
|
||||
@@ -240,6 +244,10 @@ namespace DataLayer
|
||||
Category = category;
|
||||
}
|
||||
|
||||
public override string ToString() => $"[{AudibleProductId}] {Title}";
|
||||
// needed for v3 => v4 upgrade
|
||||
public void UpdateLocale(string localeName)
|
||||
=> Locale ??= localeName;
|
||||
|
||||
public override string ToString() => $"[{AudibleProductId}] {Title}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,24 @@ namespace DataLayer
|
||||
|
||||
public DateTime DateAdded { get; private set; }
|
||||
|
||||
// immutable-ish. should be immutable. mutability is necessary for v3 => v4 upgrades
|
||||
public string Account { get; private set; }
|
||||
|
||||
private LibraryBook() { }
|
||||
public LibraryBook(Book book, DateTime dateAdded)
|
||||
public LibraryBook(Book book, DateTime dateAdded, string account)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNull(book, nameof(book));
|
||||
ArgumentValidator.EnsureNotNull(account, nameof(account));
|
||||
|
||||
Book = book;
|
||||
DateAdded = dateAdded;
|
||||
Account = account;
|
||||
}
|
||||
|
||||
public override string ToString() => $"{DateAdded:d} {Book}";
|
||||
// needed for v3 => v4 upgrade
|
||||
public void UpdateAccount(string account)
|
||||
=> Account ??= account;
|
||||
|
||||
public override string ToString() => $"{DateAdded:d} {Book}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
HOW TO CREATE: EF CORE PROJECT
|
||||
FOR QUICK MIGRATION INSTRUCTIONS:
|
||||
_DB_NOTES.txt
|
||||
|
||||
|
||||
HOW TO CREATE: EF CORE PROJECT
|
||||
==============================
|
||||
example is for sqlite but the same works with MsSql
|
||||
|
||||
|
||||
@@ -11,23 +11,23 @@ namespace DtoImporterService
|
||||
{
|
||||
public BookImporter(LibationContext context) : base(context) { }
|
||||
|
||||
public override IEnumerable<Exception> Validate(IEnumerable<Item> items) => new BookValidator().Validate(items);
|
||||
public override IEnumerable<Exception> Validate(IEnumerable<ImportItem> importItems) => new BookValidator().Validate(importItems.Select(i => i.DtoItem));
|
||||
|
||||
protected override int DoImport(IEnumerable<Item> items)
|
||||
protected override int DoImport(IEnumerable<ImportItem> importItems)
|
||||
{
|
||||
// pre-req.s
|
||||
new ContributorImporter(DbContext).Import(items);
|
||||
new SeriesImporter(DbContext).Import(items);
|
||||
new CategoryImporter(DbContext).Import(items);
|
||||
new ContributorImporter(DbContext).Import(importItems);
|
||||
new SeriesImporter(DbContext).Import(importItems);
|
||||
new CategoryImporter(DbContext).Import(importItems);
|
||||
|
||||
// get distinct
|
||||
var productIds = items.Select(i => i.ProductId).ToList();
|
||||
var productIds = importItems.Select(i => i.DtoItem.ProductId).ToList();
|
||||
|
||||
// load db existing => .Local
|
||||
loadLocal_books(productIds);
|
||||
|
||||
// upsert
|
||||
var qtyNew = upsertBooks(items);
|
||||
var qtyNew = upsertBooks(importItems);
|
||||
return qtyNew;
|
||||
}
|
||||
|
||||
@@ -44,13 +44,13 @@ namespace DtoImporterService
|
||||
DbContext.Books.GetBooks(b => remainingProductIds.Contains(b.AudibleProductId)).ToList();
|
||||
}
|
||||
|
||||
private int upsertBooks(IEnumerable<Item> items)
|
||||
private int upsertBooks(IEnumerable<ImportItem> importItems)
|
||||
{
|
||||
var qtyNew = 0;
|
||||
|
||||
foreach (var item in items)
|
||||
foreach (var item in importItems)
|
||||
{
|
||||
var book = DbContext.Books.Local.SingleOrDefault(p => p.AudibleProductId == item.ProductId);
|
||||
var book = DbContext.Books.Local.SingleOrDefault(p => p.AudibleProductId == item.DtoItem.ProductId);
|
||||
if (book is null)
|
||||
{
|
||||
book = createNewBook(item);
|
||||
@@ -63,8 +63,10 @@ namespace DtoImporterService
|
||||
return qtyNew;
|
||||
}
|
||||
|
||||
private Book createNewBook(Item item)
|
||||
private Book createNewBook(ImportItem importItem)
|
||||
{
|
||||
var item = importItem.DtoItem;
|
||||
|
||||
// absence of authors is very rare, but possible
|
||||
if (!item.Authors?.Any() ?? true)
|
||||
item.Authors = new[] { new Person { Name = "", Asin = null } };
|
||||
@@ -86,8 +88,16 @@ namespace DtoImporterService
|
||||
.ToList();
|
||||
|
||||
// categories are laid out for a breadcrumb. category is 1st, subcategory is 2nd
|
||||
// absence of categories is very rare, but possible
|
||||
var lastCategory = item.Categories.LastOrDefault()?.CategoryId ?? "";
|
||||
// absence of categories is also possible
|
||||
|
||||
// CATEGORY HACK: only use the 1st 2 categories
|
||||
// (real impl: var lastCategory = item.Categories.LastOrDefault()?.CategoryId ?? "";)
|
||||
var lastCategory
|
||||
= item.Categories.Length == 0 ? ""
|
||||
: item.Categories.Length == 1 ? item.Categories[0].CategoryId
|
||||
// 2+
|
||||
: item.Categories[1].CategoryId;
|
||||
|
||||
var category = DbContext.Categories.Local.SingleOrDefault(c => c.AudibleCategoryId == lastCategory);
|
||||
|
||||
var book = DbContext.Books.Add(new Book(
|
||||
@@ -97,7 +107,8 @@ namespace DtoImporterService
|
||||
item.LengthInMinutes,
|
||||
authors,
|
||||
narrators,
|
||||
category)
|
||||
category,
|
||||
importItem.LocaleName)
|
||||
).Entity;
|
||||
|
||||
var publisherName = item.Publisher;
|
||||
@@ -115,12 +126,17 @@ namespace DtoImporterService
|
||||
return book;
|
||||
}
|
||||
|
||||
private void updateBook(Item item, Book book)
|
||||
private void updateBook(ImportItem importItem, Book book)
|
||||
{
|
||||
var item = importItem.DtoItem;
|
||||
|
||||
// set/update book-specific info which may have changed
|
||||
book.PictureId = item.PictureId;
|
||||
book.UpdateProductRating(item.Product_OverallStars, item.Product_PerformanceStars, item.Product_StoryStars);
|
||||
|
||||
// needed during v3 => v4 migration
|
||||
book.UpdateLocale(importItem.LocaleName);
|
||||
|
||||
// important to update user-specific info. this will have changed if user has rated/reviewed the book since last library import
|
||||
book.UserDefinedItem.UpdateRating(item.MyUserRating_Overall, item.MyUserRating_Performance, item.MyUserRating_Story);
|
||||
|
||||
|
||||
@@ -11,18 +11,24 @@ namespace DtoImporterService
|
||||
{
|
||||
public CategoryImporter(LibationContext context) : base(context) { }
|
||||
|
||||
public override IEnumerable<Exception> Validate(IEnumerable<Item> items) => new CategoryValidator().Validate(items);
|
||||
public override IEnumerable<Exception> Validate(IEnumerable<ImportItem> importItems) => new CategoryValidator().Validate(importItems.Select(i => i.DtoItem));
|
||||
|
||||
protected override int DoImport(IEnumerable<Item> items)
|
||||
protected override int DoImport(IEnumerable<ImportItem> importItems)
|
||||
{
|
||||
// get distinct
|
||||
var categoryIds = items.GetCategoriesDistinct().Select(c => c.CategoryId).ToList();
|
||||
var categoryIds = importItems
|
||||
.Select(i => i.DtoItem)
|
||||
.GetCategoriesDistinct()
|
||||
.Select(c => c.CategoryId).ToList();
|
||||
|
||||
// load db existing => .Local
|
||||
loadLocal_categories(categoryIds);
|
||||
|
||||
// upsert
|
||||
var categoryPairs = items.GetCategoryPairsDistinct().ToList();
|
||||
var categoryPairs = importItems
|
||||
.Select(i => i.DtoItem)
|
||||
.GetCategoryPairsDistinct()
|
||||
.ToList();
|
||||
var qtyNew = upsertCategories(categoryPairs);
|
||||
return qtyNew;
|
||||
}
|
||||
@@ -51,6 +57,10 @@ namespace DtoImporterService
|
||||
{
|
||||
for (var i = 0; i < pair.Length; i++)
|
||||
{
|
||||
// CATEGORY HACK: not yet supported: depth beyond 0 and 1
|
||||
if (i > 1)
|
||||
break;
|
||||
|
||||
var id = pair[i].CategoryId;
|
||||
var name = pair[i].CategoryName;
|
||||
|
||||
|
||||
@@ -11,14 +11,23 @@ namespace DtoImporterService
|
||||
{
|
||||
public ContributorImporter(LibationContext context) : base(context) { }
|
||||
|
||||
public override IEnumerable<Exception> Validate(IEnumerable<Item> items) => new ContributorValidator().Validate(items);
|
||||
public override IEnumerable<Exception> Validate(IEnumerable<ImportItem> importItems) => new ContributorValidator().Validate(importItems.Select(i => i.DtoItem));
|
||||
|
||||
protected override int DoImport(IEnumerable<Item> items)
|
||||
protected override int DoImport(IEnumerable<ImportItem> importItems)
|
||||
{
|
||||
// get distinct
|
||||
var authors = items.GetAuthorsDistinct().ToList();
|
||||
var narrators = items.GetNarratorsDistinct().ToList();
|
||||
var publishers = items.GetPublishersDistinct().ToList();
|
||||
var authors = importItems
|
||||
.Select(i => i.DtoItem)
|
||||
.GetAuthorsDistinct()
|
||||
.ToList();
|
||||
var narrators = importItems
|
||||
.Select(i => i.DtoItem)
|
||||
.GetNarratorsDistinct()
|
||||
.ToList();
|
||||
var publishers = importItems
|
||||
.Select(i => i.DtoItem)
|
||||
.GetPublishersDistinct()
|
||||
.ToList();
|
||||
|
||||
// load db existing => .Local
|
||||
var allNames = publishers
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AudibleApiDTOs;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using InternalUtilities;
|
||||
|
||||
namespace DtoImporterService
|
||||
{
|
||||
@@ -11,7 +11,7 @@ namespace DtoImporterService
|
||||
{
|
||||
protected LibationContext DbContext { get; }
|
||||
|
||||
public ImporterBase(LibationContext context)
|
||||
protected ImporterBase(LibationContext context)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNull(context, nameof(context));
|
||||
DbContext = context;
|
||||
@@ -50,8 +50,8 @@ namespace DtoImporterService
|
||||
public abstract IEnumerable<Exception> Validate(T param);
|
||||
}
|
||||
|
||||
public abstract class ItemsImporterBase : ImporterBase<IEnumerable<Item>>
|
||||
public abstract class ItemsImporterBase : ImporterBase<IEnumerable<ImportItem>>
|
||||
{
|
||||
public ItemsImporterBase(LibationContext context) : base(context) { }
|
||||
protected ItemsImporterBase(LibationContext context) : base(context) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,29 +11,50 @@ namespace DtoImporterService
|
||||
{
|
||||
public LibraryImporter(LibationContext context) : base(context) { }
|
||||
|
||||
public override IEnumerable<Exception> Validate(IEnumerable<Item> items) => new LibraryValidator().Validate(items);
|
||||
public override IEnumerable<Exception> Validate(IEnumerable<ImportItem> importItems) => new LibraryValidator().Validate(importItems.Select(i => i.DtoItem));
|
||||
|
||||
protected override int DoImport(IEnumerable<Item> items)
|
||||
protected override int DoImport(IEnumerable<ImportItem> importItems)
|
||||
{
|
||||
new BookImporter(DbContext).Import(items);
|
||||
new BookImporter(DbContext).Import(importItems);
|
||||
|
||||
var qtyNew = upsertLibraryBooks(items);
|
||||
var qtyNew = upsertLibraryBooks(importItems);
|
||||
return qtyNew;
|
||||
}
|
||||
|
||||
private int upsertLibraryBooks(IEnumerable<Item> items)
|
||||
private int upsertLibraryBooks(IEnumerable<ImportItem> importItems)
|
||||
{
|
||||
// technically, we should be able to have duplicate books from separate accounts.
|
||||
// this would violate the current pk and would be difficult to deal with elsewhere:
|
||||
// - what to show in the grid
|
||||
// - which to consider liberated
|
||||
//
|
||||
// sqlite cannot alter pk. the work around is an extensive headache. it'll be fixed in pre .net5/efcore5
|
||||
//
|
||||
// currently, inserting LibraryBook will throw error if the same book is in multiple accounts for the same region.
|
||||
//
|
||||
// CURRENT SOLUTION: don't re-insert
|
||||
|
||||
var currentLibraryProductIds = DbContext.Library.Select(l => l.Book.AudibleProductId).ToList();
|
||||
var newItems = items.Where(dto => !currentLibraryProductIds.Contains(dto.ProductId)).ToList();
|
||||
var newItems = importItems.Where(dto => !currentLibraryProductIds.Contains(dto.DtoItem.ProductId)).ToList();
|
||||
|
||||
foreach (var newItem in newItems)
|
||||
{
|
||||
var libraryBook = new LibraryBook(
|
||||
DbContext.Books.Local.Single(b => b.AudibleProductId == newItem.ProductId),
|
||||
newItem.DateAdded);
|
||||
DbContext.Books.Local.Single(b => b.AudibleProductId == newItem.DtoItem.ProductId),
|
||||
newItem.DtoItem.DateAdded,
|
||||
newItem.AccountId);
|
||||
DbContext.Library.Add(libraryBook);
|
||||
}
|
||||
|
||||
// needed for v3 => v4 upgrade
|
||||
var toUpdate = DbContext.Library.Where(l => l.Account == null);
|
||||
foreach (var u in toUpdate)
|
||||
{
|
||||
var item = importItems.FirstOrDefault(ii => ii.DtoItem.ProductId == u.Book.AudibleProductId);
|
||||
if (item != null)
|
||||
u.UpdateAccount(item.AccountId);
|
||||
}
|
||||
|
||||
var qtyNew = newItems.Count;
|
||||
return qtyNew;
|
||||
}
|
||||
|
||||
@@ -11,12 +11,15 @@ namespace DtoImporterService
|
||||
{
|
||||
public SeriesImporter(LibationContext context) : base(context) { }
|
||||
|
||||
public override IEnumerable<Exception> Validate(IEnumerable<Item> items) => new SeriesValidator().Validate(items);
|
||||
public override IEnumerable<Exception> Validate(IEnumerable<ImportItem> importItems) => new SeriesValidator().Validate(importItems.Select(i => i.DtoItem));
|
||||
|
||||
protected override int DoImport(IEnumerable<Item> items)
|
||||
protected override int DoImport(IEnumerable<ImportItem> importItems)
|
||||
{
|
||||
// get distinct
|
||||
var series = items.GetSeriesDistinct().ToList();
|
||||
var series = importItems
|
||||
.Select(i => i.DtoItem)
|
||||
.GetSeriesDistinct()
|
||||
.ToList();
|
||||
|
||||
// load db existing => .Local
|
||||
loadLocal_series(series);
|
||||
|
||||
@@ -8,6 +8,7 @@ using DataLayer;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.ErrorHandling;
|
||||
using FileManager;
|
||||
using InternalUtilities;
|
||||
|
||||
namespace FileLiberator
|
||||
{
|
||||
@@ -55,8 +56,7 @@ namespace FileLiberator
|
||||
if (AudibleFileStorage.Audio.Exists(libraryBook.Book.AudibleProductId))
|
||||
return new StatusHandler { "Cannot find decrypt. Final audio file already exists" };
|
||||
|
||||
var proposedOutputFile = Path.Combine(AudibleFileStorage.DecryptInProgress, $"[{libraryBook.Book.AudibleProductId}].m4b");
|
||||
var outputAudioFilename = await aaxToM4bConverterDecrypt(proposedOutputFile, aaxFilename);
|
||||
var outputAudioFilename = await aaxToM4bConverterDecrypt(aaxFilename, libraryBook);
|
||||
|
||||
// decrypt failed
|
||||
if (outputAudioFilename == null)
|
||||
@@ -78,13 +78,19 @@ namespace FileLiberator
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> aaxToM4bConverterDecrypt(string proposedOutputFile, string aaxFilename)
|
||||
private async Task<string> aaxToM4bConverterDecrypt(string aaxFilename, LibraryBook libraryBook)
|
||||
{
|
||||
DecryptBegin?.Invoke(this, $"Begin decrypting {aaxFilename}");
|
||||
|
||||
try
|
||||
{
|
||||
var converter = await AaxToM4bConverter.CreateAsync(aaxFilename, Configuration.Instance.DecryptKey);
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
|
||||
var account = persister
|
||||
.AccountsSettings
|
||||
.GetAccount(libraryBook.Account, libraryBook.Book.Locale);
|
||||
|
||||
var converter = await AaxToM4bConverter.CreateAsync(aaxFilename, account.DecryptKey);
|
||||
converter.AppName = "Libation";
|
||||
|
||||
TitleDiscovered?.Invoke(this, converter.tags.title);
|
||||
@@ -92,7 +98,8 @@ namespace FileLiberator
|
||||
NarratorsDiscovered?.Invoke(this, converter.tags.narrator);
|
||||
CoverImageFilepathDiscovered?.Invoke(this, converter.coverBytes);
|
||||
|
||||
// override default which was set in CreateAsync
|
||||
// override default which was set in CreateAsync
|
||||
var proposedOutputFile = Path.Combine(AudibleFileStorage.DecryptInProgress, $"[{libraryBook.Book.AudibleProductId}].m4b");
|
||||
converter.SetOutputFilename(proposedOutputFile);
|
||||
converter.DecryptProgressUpdate += (s, progress) => UpdateProgress?.Invoke(this, progress);
|
||||
|
||||
@@ -103,7 +110,7 @@ namespace FileLiberator
|
||||
if (!success)
|
||||
return null;
|
||||
|
||||
Configuration.Instance.DecryptKey = converter.decryptKey;
|
||||
account.DecryptKey = converter.decryptKey;
|
||||
|
||||
return converter.outputFileName;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using AudibleApi;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.ErrorHandling;
|
||||
using FileManager;
|
||||
using InternalUtilities;
|
||||
|
||||
namespace FileLiberator
|
||||
{
|
||||
@@ -18,6 +20,8 @@ namespace FileLiberator
|
||||
/// </summary>
|
||||
public class DownloadBook : DownloadableBase
|
||||
{
|
||||
private const string SERVICE_UNAVAILABLE = "Content Delivery Companion Service is not available.";
|
||||
|
||||
public override bool Validate(LibraryBook libraryBook)
|
||||
=> !AudibleFileStorage.Audio.Exists(libraryBook.Book.AudibleProductId)
|
||||
&& !AudibleFileStorage.AAX.Exists(libraryBook.Book.AudibleProductId);
|
||||
@@ -39,7 +43,9 @@ namespace FileLiberator
|
||||
|
||||
private async Task<string> downloadBookAsync(LibraryBook libraryBook, string tempAaxFilename)
|
||||
{
|
||||
var api = await AudibleApi.EzApiCreator.GetApiAsync(AudibleApiStorage.IdentityTokensFile);
|
||||
validate(libraryBook);
|
||||
|
||||
var api = await AudibleApiActions.GetApiAsync(libraryBook.Account, libraryBook.Book.Locale);
|
||||
|
||||
var actualFilePath = await PerformDownloadAsync(
|
||||
tempAaxFilename,
|
||||
@@ -48,18 +54,53 @@ namespace FileLiberator
|
||||
System.Threading.Thread.Sleep(100);
|
||||
// if bad file download, a 0-33 byte file will be created
|
||||
// if service unavailable, a 52 byte string will be saved as file
|
||||
if (new FileInfo(actualFilePath).Length < 100)
|
||||
var length = new FileInfo(actualFilePath).Length;
|
||||
|
||||
if (length > 100)
|
||||
return actualFilePath;
|
||||
|
||||
var contents = File.ReadAllText(actualFilePath);
|
||||
File.Delete(actualFilePath);
|
||||
|
||||
var exMsg = contents.StartsWithInsensitive(SERVICE_UNAVAILABLE)
|
||||
? SERVICE_UNAVAILABLE
|
||||
: "Error downloading file";
|
||||
|
||||
var ex = new Exception(exMsg);
|
||||
Serilog.Log.Logger.Error(ex, "Download error {@DebugInfo}", new
|
||||
{
|
||||
var contents = File.ReadAllText(actualFilePath);
|
||||
File.Delete(actualFilePath);
|
||||
libraryBook.Book.Title,
|
||||
libraryBook.Book.AudibleProductId,
|
||||
libraryBook.Book.Locale,
|
||||
libraryBook.Account,
|
||||
tempAaxFilename,
|
||||
actualFilePath,
|
||||
length,
|
||||
contents
|
||||
});
|
||||
throw ex;
|
||||
}
|
||||
|
||||
var unavailable = "Content Delivery Companion Service is not available.";
|
||||
if (contents.StartsWithInsensitive(unavailable))
|
||||
throw new Exception(unavailable);
|
||||
throw new Exception("Error downloading file");
|
||||
}
|
||||
private static void validate(LibraryBook libraryBook)
|
||||
{
|
||||
string errorString(string field)
|
||||
=> $"{errorTitle()}\r\nCannot download book. {field} is not known. Try re-importing the account which owns this book.";
|
||||
|
||||
return actualFilePath;
|
||||
string errorTitle()
|
||||
{
|
||||
var title
|
||||
= (libraryBook.Book.Title.Length > 53)
|
||||
? $"{libraryBook.Book.Title.Truncate(50)}..."
|
||||
: libraryBook.Book.Title;
|
||||
var errorBookTitle = $"{title} [{libraryBook.Book.AudibleProductId}]";
|
||||
return errorBookTitle;
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(libraryBook.Account))
|
||||
throw new Exception(errorString("Account"));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(libraryBook.Book.Locale))
|
||||
throw new Exception(errorString("Locale"));
|
||||
}
|
||||
|
||||
private void moveBook(LibraryBook libraryBook, string actualFilePath)
|
||||
|
||||
@@ -46,6 +46,14 @@ namespace FileLiberator
|
||||
|
||||
private static async Task<StatusHandler> processBookAsync(IProcessable processable, LibraryBook libraryBook)
|
||||
{
|
||||
Serilog.Log.Information("Begin " + nameof(processBookAsync) + " {@DebugInfo}", new
|
||||
{
|
||||
libraryBook.Book.Title,
|
||||
libraryBook.Book.AudibleProductId,
|
||||
libraryBook.Book.Locale,
|
||||
libraryBook.Account
|
||||
});
|
||||
|
||||
// this should never happen. check anyway. ProcessFirstValidAsync returning null is the signal that we're done. we can't let another IProcessable accidentally send this command
|
||||
var status = await processable.ProcessAsync(libraryBook);
|
||||
if (status == null)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Polly" Version="7.2.0" />
|
||||
<PackageReference Include="Polly" Version="7.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace FileManager
|
||||
{
|
||||
public static class AudibleApiStorage
|
||||
{
|
||||
// not customizable. don't move to config
|
||||
public static string IdentityTokensFile => Path.Combine(Configuration.Instance.LibationFiles, "IdentityTokens.json");
|
||||
}
|
||||
}
|
||||
@@ -37,18 +37,11 @@ namespace FileManager
|
||||
|
||||
public bool FilesExist
|
||||
=> File.Exists(APPSETTINGS_JSON)
|
||||
&& File.Exists(SettingsJsonPath)
|
||||
&& File.Exists(SettingsFilePath)
|
||||
&& Directory.Exists(LibationFiles)
|
||||
&& Directory.Exists(Books);
|
||||
|
||||
public string SettingsJsonPath => Path.Combine(LibationFiles, "Settings.json");
|
||||
|
||||
[Description("Your user-specific key used to decrypt your audible files (*.aax) into audio files you can use anywhere (*.m4b). Leave alone in most cases")]
|
||||
public string DecryptKey
|
||||
{
|
||||
get => persistentDictionary.GetString(nameof(DecryptKey));
|
||||
set => persistentDictionary.Set(nameof(DecryptKey), value);
|
||||
}
|
||||
public string SettingsFilePath => Path.Combine(LibationFiles, "Settings.json");
|
||||
|
||||
[Description("Location for book storage. Includes destination of newly liberated books")]
|
||||
public string Books
|
||||
@@ -90,12 +83,6 @@ namespace FileManager
|
||||
set => persistentDictionary.Set(nameof(DecryptInProgressEnum), value);
|
||||
}
|
||||
|
||||
public string LocaleCountryCode
|
||||
{
|
||||
get => persistentDictionary.GetString(nameof(LocaleCountryCode));
|
||||
set => persistentDictionary.Set(nameof(LocaleCountryCode), value);
|
||||
}
|
||||
|
||||
// note: any potential file manager static ctors can't compensate if storage dir is changed at run time via settings. this is partly bad architecture. but the side effect is desirable. if changing LibationFiles location: restart app
|
||||
|
||||
// singleton stuff
|
||||
@@ -115,11 +102,11 @@ namespace FileManager
|
||||
if (wellKnownPaths.ContainsKey(value))
|
||||
value = wellKnownPaths[value];
|
||||
|
||||
// must write here before SettingsJsonPath in next step reads cache
|
||||
// must write here before SettingsFilePath in next step reads cache
|
||||
libationFilesPathCache = value;
|
||||
|
||||
// load json values into memory. create if not exists
|
||||
persistentDictionary = new PersistentDictionary(SettingsJsonPath);
|
||||
persistentDictionary = new PersistentDictionary(SettingsFilePath);
|
||||
|
||||
return libationFilesPathCache;
|
||||
}
|
||||
@@ -171,7 +158,7 @@ namespace FileManager
|
||||
// if moving from default, delete old settings file and dir (if empty)
|
||||
if (LibationFiles.EqualsInsensitive(AppDir))
|
||||
{
|
||||
File.Delete(SettingsJsonPath);
|
||||
File.Delete(SettingsFilePath);
|
||||
System.Threading.Thread.Sleep(100);
|
||||
if (!Directory.EnumerateDirectories(AppDir).Any() && !Directory.EnumerateFiles(AppDir).Any())
|
||||
Directory.Delete(AppDir);
|
||||
|
||||
97
InternalUtilities/Account.cs
Normal file
97
InternalUtilities/Account.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AudibleApi;
|
||||
using AudibleApi.Authorization;
|
||||
using Dinah.Core;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace InternalUtilities
|
||||
{
|
||||
public class Account : IUpdatable
|
||||
{
|
||||
public event EventHandler Updated;
|
||||
private void update(object sender = null, EventArgs e = null)
|
||||
=> Updated?.Invoke(this, new EventArgs());
|
||||
|
||||
// canonical. immutable. email or phone number
|
||||
public string AccountId { get; }
|
||||
|
||||
// user-friendly, non-canonical name. mutable
|
||||
private string _accountName;
|
||||
public string AccountName
|
||||
{
|
||||
get => _accountName;
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return;
|
||||
var v = value.Trim();
|
||||
if (v == _accountName)
|
||||
return;
|
||||
_accountName = v;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
// whether to include this account when scanning libraries.
|
||||
// technically this is an app setting; not an attribute of account. but since it's managed with accounts, it makes sense to put this exception-to-the-rule here
|
||||
private bool _libraryScan = true;
|
||||
public bool LibraryScan
|
||||
{
|
||||
get => _libraryScan;
|
||||
set
|
||||
{
|
||||
if (value == _libraryScan)
|
||||
return;
|
||||
_libraryScan = value;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
private string _decryptKey = "";
|
||||
/// <summary>aka: activation bytes</summary>
|
||||
public string DecryptKey
|
||||
{
|
||||
get => _decryptKey;
|
||||
set
|
||||
{
|
||||
var v = (value ?? "").Trim();
|
||||
if (v == _decryptKey)
|
||||
return;
|
||||
_decryptKey = v;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
private Identity _identity;
|
||||
public Identity IdentityTokens
|
||||
{
|
||||
get => _identity;
|
||||
set
|
||||
{
|
||||
if (_identity is null && value is null)
|
||||
return;
|
||||
|
||||
if (_identity != null)
|
||||
_identity.Updated -= update;
|
||||
|
||||
if (value != null)
|
||||
value.Updated += update;
|
||||
|
||||
_identity = value;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public Locale Locale => IdentityTokens?.Locale;
|
||||
|
||||
public Account(string accountId)
|
||||
{
|
||||
AccountId = ArgumentValidator.EnsureNotNullOrWhiteSpace(accountId, nameof(accountId)).Trim();
|
||||
}
|
||||
|
||||
public override string ToString() => $"{AccountId} - {Locale?.Name ?? "[empty]"}";
|
||||
}
|
||||
}
|
||||
142
InternalUtilities/AccountsSettings.cs
Normal file
142
InternalUtilities/AccountsSettings.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AudibleApi;
|
||||
using AudibleApi.Authorization;
|
||||
using Dinah.Core;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace InternalUtilities
|
||||
{
|
||||
// 'AccountsSettings' is intentionally NOT IEnumerable<> so that properties can be added/extended
|
||||
// from newtonsoft (https://www.newtonsoft.com/json/help/html/SerializationGuide.htm):
|
||||
// .NET : IList, IEnumerable, IList<T>, Array
|
||||
// JSON : Array (properties on the collection will not be serialized)
|
||||
public class AccountsSettings : IUpdatable
|
||||
{
|
||||
public event EventHandler Updated;
|
||||
private void update(object sender = null, EventArgs e = null)
|
||||
{
|
||||
foreach (var account in Accounts)
|
||||
validate(account);
|
||||
update_no_validate();
|
||||
}
|
||||
private void update_no_validate() => Updated?.Invoke(this, new EventArgs());
|
||||
|
||||
public AccountsSettings() { }
|
||||
|
||||
// for some reason this will make the json instantiator use _accounts_json.set()
|
||||
[JsonConstructor]
|
||||
protected AccountsSettings(List<Account> accountsSettings) { }
|
||||
|
||||
#region Accounts
|
||||
private List<Account> _accounts_backing = new List<Account>();
|
||||
[JsonProperty(PropertyName = nameof(Accounts))]
|
||||
private List<Account> _accounts_json
|
||||
{
|
||||
get => _accounts_backing;
|
||||
// 'set' is only used by json deser
|
||||
set
|
||||
{
|
||||
if (value is null)
|
||||
return;
|
||||
|
||||
foreach (var account in value)
|
||||
_add(account);
|
||||
|
||||
update_no_validate();
|
||||
}
|
||||
}
|
||||
[JsonIgnore]
|
||||
public IReadOnlyList<Account> Accounts => _accounts_json.AsReadOnly();
|
||||
#endregion
|
||||
|
||||
#region de/serialize
|
||||
public static AccountsSettings FromJson(string json)
|
||||
=> JsonConvert.DeserializeObject<AccountsSettings>(json, Identity.GetJsonSerializerSettings());
|
||||
|
||||
public string ToJson(Formatting formatting = Formatting.Indented)
|
||||
=> JsonConvert.SerializeObject(this, formatting, Identity.GetJsonSerializerSettings());
|
||||
#endregion
|
||||
|
||||
// more common naming convention alias for internal collection
|
||||
public IReadOnlyList<Account> GetAll() => Accounts;
|
||||
|
||||
public Account Upsert(string accountId, string locale)
|
||||
{
|
||||
var acct = GetAccount(accountId, locale);
|
||||
|
||||
if (acct != null)
|
||||
return acct;
|
||||
|
||||
var l = Localization.Get(locale);
|
||||
var id = new Identity(l);
|
||||
|
||||
var account = new Account(accountId) { IdentityTokens = id };
|
||||
Add(account);
|
||||
return account;
|
||||
}
|
||||
|
||||
public void Add(Account account)
|
||||
{
|
||||
_add(account);
|
||||
update_no_validate();
|
||||
}
|
||||
|
||||
public void _add(Account account)
|
||||
{
|
||||
validate(account);
|
||||
|
||||
_accounts_backing.Add(account);
|
||||
account.Updated += update;
|
||||
}
|
||||
|
||||
public Account GetAccount(string accountId, string locale)
|
||||
{
|
||||
if (locale is null)
|
||||
return null;
|
||||
|
||||
return Accounts.SingleOrDefault(a => a.AccountId == accountId && a.IdentityTokens.Locale.Name == locale);
|
||||
}
|
||||
|
||||
public bool Delete(string accountId, string locale)
|
||||
{
|
||||
var acct = GetAccount(accountId, locale);
|
||||
if (acct is null)
|
||||
return false;
|
||||
return Delete(acct);
|
||||
}
|
||||
|
||||
public bool Delete(Account account)
|
||||
{
|
||||
if (!_accounts_backing.Contains(account))
|
||||
return false;
|
||||
|
||||
account.Updated -= update;
|
||||
var result = _accounts_backing.Remove(account);
|
||||
update_no_validate();
|
||||
return result;
|
||||
}
|
||||
|
||||
private void validate(Account account)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNull(account, nameof(account));
|
||||
|
||||
var accountId = account.AccountId;
|
||||
var locale = account?.IdentityTokens?.Locale?.Name;
|
||||
|
||||
var acct = GetAccount(accountId, locale);
|
||||
|
||||
// new: ok
|
||||
if (acct is null)
|
||||
return;
|
||||
|
||||
// same account instance: ok
|
||||
if (acct == account)
|
||||
return;
|
||||
|
||||
// same account id + locale, different instance: bad
|
||||
throw new InvalidOperationException("Cannot add an account with the same account Id and Locale");
|
||||
}
|
||||
}
|
||||
}
|
||||
24
InternalUtilities/AccountsSettingsPersister.cs
Normal file
24
InternalUtilities/AccountsSettingsPersister.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using AudibleApi.Authorization;
|
||||
using Dinah.Core.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace InternalUtilities
|
||||
{
|
||||
public class AccountsSettingsPersister : JsonFilePersister<AccountsSettings>
|
||||
{
|
||||
/// <summary>Alias for Target </summary>
|
||||
public AccountsSettings AccountsSettings => Target;
|
||||
|
||||
/// <summary>uses path. create file if doesn't yet exist</summary>
|
||||
public AccountsSettingsPersister(AccountsSettings target, string path, string jsonPath = null)
|
||||
: base(target, path, jsonPath) { }
|
||||
|
||||
/// <summary>load from existing file</summary>
|
||||
public AccountsSettingsPersister(string path, string jsonPath = null)
|
||||
: base(path, jsonPath) { }
|
||||
|
||||
protected override JsonSerializerSettings GetSerializerSettings()
|
||||
=> Identity.GetJsonSerializerSettings();
|
||||
}
|
||||
}
|
||||
12
InternalUtilities/ImportItem.cs
Normal file
12
InternalUtilities/ImportItem.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using AudibleApiDTOs;
|
||||
|
||||
namespace InternalUtilities
|
||||
{
|
||||
public class ImportItem
|
||||
{
|
||||
public Item DtoItem { get; set; }
|
||||
public string AccountId { get; set; }
|
||||
public string LocaleName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,35 +4,63 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AudibleApi;
|
||||
using AudibleApiDTOs;
|
||||
using FileManager;
|
||||
using Polly;
|
||||
using Polly.Retry;
|
||||
|
||||
namespace InternalUtilities
|
||||
{
|
||||
public class AudibleApiActions
|
||||
public static class AudibleApiActions
|
||||
{
|
||||
private AsyncRetryPolicy policy { get; }
|
||||
/// <summary>USE THIS from within Libation. It wraps the call with correct JSONPath</summary>
|
||||
public static Task<Api> GetApiAsync(string username, string localeName, ILoginCallback loginCallback = null)
|
||||
{
|
||||
Serilog.Log.Logger.Information("GetApiAsync. {@DebugInfo}", new
|
||||
{
|
||||
username,
|
||||
LocaleName = localeName,
|
||||
});
|
||||
return EzApiCreator.GetApiAsync(
|
||||
Localization.Get(localeName),
|
||||
AudibleApiStorage.AccountsSettingsFile,
|
||||
AudibleApiStorage.GetIdentityTokensJsonPath(username, localeName),
|
||||
loginCallback);
|
||||
}
|
||||
|
||||
/// <summary>USE THIS from within Libation. It wraps the call with correct JSONPath</summary>
|
||||
public static Task<Api> GetApiAsync(ILoginCallback loginCallback, Account account)
|
||||
{
|
||||
Serilog.Log.Logger.Information("GetApiAsync. {@DebugInfo}", new
|
||||
{
|
||||
AccountId = account?.AccountId ?? "[empty]",
|
||||
LocaleName = account?.Locale?.Name
|
||||
});
|
||||
return EzApiCreator.GetApiAsync(
|
||||
account.Locale,
|
||||
AudibleApiStorage.AccountsSettingsFile,
|
||||
account.GetIdentityTokensJsonPath(),
|
||||
loginCallback);
|
||||
}
|
||||
|
||||
private static AsyncRetryPolicy policy { get; }
|
||||
= Policy.Handle<Exception>()
|
||||
// 2 retries == 3 total
|
||||
.RetryAsync(2);
|
||||
|
||||
public async Task<List<Item>> GetAllLibraryItemsAsync(ILoginCallback callback)
|
||||
public static Task<List<Item>> GetLibraryValidatedAsync(Api api)
|
||||
{
|
||||
// bug on audible's side. the 1st time after a long absence, a query to get library will return without titles or authors. a subsequent identical query will be successful. this is true whether or tokens are refreshed
|
||||
// worse, this 1st dummy call doesn't seem to help:
|
||||
// var page = await api.GetLibraryAsync(new AudibleApi.LibraryOptions { NumberOfResultPerPage = 1, PageNumber = 1, PurchasedAfter = DateTime.Now.AddYears(-20), ResponseGroups = AudibleApi.LibraryOptions.ResponseGroupOptions.ALL_OPTIONS });
|
||||
// i don't want to incur the cost of making a full dummy call every time because it fails sometimes
|
||||
return await policy.ExecuteAsync(() => getItemsAsync(callback));
|
||||
return policy.ExecuteAsync(() => getItemsAsync(api));
|
||||
}
|
||||
|
||||
private async Task<List<Item>> getItemsAsync(ILoginCallback callback)
|
||||
private static async Task<List<Item>> getItemsAsync(Api api)
|
||||
{
|
||||
var api = await EzApiCreator.GetApiAsync(AudibleApiStorage.IdentityTokensFile, callback, Configuration.Instance.LocaleCountryCode);
|
||||
var items = await AudibleApiExtensions.GetAllLibraryItemsAsync(api);
|
||||
var items = await api.GetAllLibraryItemsAsync();
|
||||
|
||||
// remove episode parents
|
||||
items.RemoveAll(i => i.IsEpisodes);
|
||||
// remove episode parents and 'audible plus' check-outs
|
||||
items.RemoveAll(i => i.IsEpisodes || i.IsNonLibraryAudiblePlus);
|
||||
|
||||
#region // episode handling. doesn't quite work
|
||||
// // add individual/children episodes
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AudibleApi;
|
||||
using AudibleApiDTOs;
|
||||
|
||||
//
|
||||
// probably not the best place for this
|
||||
// but good enough for now
|
||||
//
|
||||
namespace InternalUtilities
|
||||
{
|
||||
public static class AudibleApiExtensions
|
||||
{
|
||||
public static async Task<List<Item>> GetAllLibraryItemsAsync(this Api api)
|
||||
{
|
||||
var allItems = new List<Item>();
|
||||
|
||||
for (var i = 1; ; i++)
|
||||
{
|
||||
var page = await api.GetLibraryAsync(new LibraryOptions
|
||||
{
|
||||
NumberOfResultPerPage = 1000,
|
||||
PageNumber = i,
|
||||
PurchasedAfter = new DateTime(2000, 1, 1),
|
||||
ResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS
|
||||
});
|
||||
|
||||
var pageStr = page.ToString();
|
||||
|
||||
LibraryDtoV10 libResult;
|
||||
try
|
||||
{
|
||||
// important! use this convert method
|
||||
libResult = LibraryDtoV10.FromJson(pageStr);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "Error converting library for importing use. Full library:\r\n" + pageStr);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (!libResult.Items.Any())
|
||||
break;
|
||||
else
|
||||
Serilog.Log.Logger.Information($"Page {i}: {libResult.Items.Length} results");
|
||||
|
||||
allItems.AddRange(libResult.Items);
|
||||
}
|
||||
|
||||
return allItems;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
InternalUtilities/UNTESTED/AudibleApiStorage.cs
Normal file
47
InternalUtilities/UNTESTED/AudibleApiStorage.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using FileManager;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace InternalUtilities
|
||||
{
|
||||
public static class AudibleApiStorage
|
||||
{
|
||||
public static string AccountsSettingsFile => Path.Combine(Configuration.Instance.LibationFiles, "AccountsSettings.json");
|
||||
|
||||
public static void EnsureAccountsSettingsFileExists()
|
||||
{
|
||||
// saves. BEWARE: this will overwrite an existing file
|
||||
if (!File.Exists(AccountsSettingsFile))
|
||||
_ = new AccountsSettingsPersister(new AccountsSettings(), AccountsSettingsFile);
|
||||
}
|
||||
|
||||
/// <summary>If you use this, be a good citizen and DISPOSE of it</summary>
|
||||
public static AccountsSettingsPersister GetAccountsSettingsPersister() => new AccountsSettingsPersister(AccountsSettingsFile);
|
||||
|
||||
public static string GetIdentityTokensJsonPath(this Account account)
|
||||
=> GetIdentityTokensJsonPath(account.AccountId, account.Locale?.Name);
|
||||
public static string GetIdentityTokensJsonPath(string username, string localeName)
|
||||
{
|
||||
var usernameSanitized = trimSurroundingQuotes(JsonConvert.ToString(username));
|
||||
var localeNameSanitized = trimSurroundingQuotes(JsonConvert.ToString(localeName));
|
||||
|
||||
return $"$.Accounts[?(@.AccountId == '{usernameSanitized}' && @.IdentityTokens.LocaleName == '{localeNameSanitized}')].IdentityTokens";
|
||||
}
|
||||
private static string trimSurroundingQuotes(string str)
|
||||
{
|
||||
// SubString algo is better than .Trim("\"")
|
||||
// orig string "
|
||||
// json string "\""
|
||||
// Eg:
|
||||
// => str.Trim("\"")
|
||||
// output \
|
||||
// vs
|
||||
// => str.Substring(1, str.Length - 2)
|
||||
// output \"
|
||||
// also works with surrounding single quotes
|
||||
|
||||
return str.Substring(1, str.Length - 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,9 +51,6 @@ namespace InternalUtilities
|
||||
if (distinct.Any(s => s.CategoryName is null))
|
||||
exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Categories)} with null {nameof(Ladder.CategoryName)}", nameof(items)));
|
||||
|
||||
if (items.GetCategoryPairsDistinct().Any(p => p.Length > 2))
|
||||
exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Categories)} with wrong number of categories. Expecting 0, 1, or 2 categories per title", nameof(items)));
|
||||
|
||||
return exceptions;
|
||||
}
|
||||
}
|
||||
|
||||
19
Libation.sln
19
Libation.sln
@@ -10,13 +10,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Solution Items", "_Solutio
|
||||
REFERENCE.txt = REFERENCE.txt
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3.2 Domain Utilities (database aware)", "3.2 Domain Utilities (database aware)", "{41CDCC73-9B81-49DD-9570-C54406E852AF}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5 Domain Utilities (db aware)", "5 Domain Utilities (db aware)", "{41CDCC73-9B81-49DD-9570-C54406E852AF}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4 Application", "4 Application", "{8679CAC8-9164-4007-BDD2-F004810EDA14}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "6 Application", "6 Application", "{8679CAC8-9164-4007-BDD2-F004810EDA14}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1 Core Libraries", "1 Core Libraries", "{43E3ACB3-E0BC-4370-8DBB-E3720C8C8FD1}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3 Domain", "3 Domain", "{751093DD-5DBA-463E-ADBE-E05FAFB6983E}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4 Domain (db)", "4 Domain (db)", "{751093DD-5DBA-463E-ADBE-E05FAFB6983E}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2 Utilities (domain ignorant)", "2 Utilities (domain ignorant)", "{7FBBB086-0807-4998-85BF-6D1A49C8AD05}"
|
||||
EndProject
|
||||
@@ -30,7 +30,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileLiberator", "FileLibera
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InternalUtilities", "InternalUtilities\InternalUtilities.csproj", "{06882742-27A6-4347-97D9-56162CEC9C11}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3.1 Domain Internal Utilities (db ignorant)", "3.1 Domain Internal Utilities (db ignorant)", "{F0CBB7A7-D3FB-41FF-8F47-CF3F6A592249}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3 Domain Internal Utilities (db ignorant)", "3 Domain Internal Utilities (db ignorant)", "{F0CBB7A7-D3FB-41FF-8F47-CF3F6A592249}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationSearchEngine", "LibationSearchEngine\LibationSearchEngine.csproj", "{2E1F5DB4-40CC-4804-A893-5DCE0193E598}"
|
||||
EndProject
|
||||
@@ -80,7 +80,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dinah.Core.WindowsDesktop",
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsDesktopUtilities", "WindowsDesktopUtilities\WindowsDesktopUtilities.csproj", "{E7EFD64D-6630-4426-B09C-B6862A92E3FD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibationLauncher", "LibationLauncher\LibationLauncher.csproj", "{F3B04A3A-20C8-4582-A54A-715AF6A5D859}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationLauncher", "LibationLauncher\LibationLauncher.csproj", "{F3B04A3A-20C8-4582-A54A-715AF6A5D859}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "0 Libation Tests", "0 Libation Tests", "{67E66E82-5532-4440-AFB3-9FB1DF9DEF53}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InternalUtilities.Tests", "_Tests\InternalUtilities.Tests\InternalUtilities.Tests.csproj", "{8447C956-B03E-4F59-9DD4-877793B849D9}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -200,6 +204,10 @@ Global
|
||||
{F3B04A3A-20C8-4582-A54A-715AF6A5D859}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F3B04A3A-20C8-4582-A54A-715AF6A5D859}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F3B04A3A-20C8-4582-A54A-715AF6A5D859}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8447C956-B03E-4F59-9DD4-877793B849D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8447C956-B03E-4F59-9DD4-877793B849D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8447C956-B03E-4F59-9DD4-877793B849D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8447C956-B03E-4F59-9DD4-877793B849D9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -233,6 +241,7 @@ Global
|
||||
{059CE32C-9AD6-45E9-A166-790DFFB0B730} = {43E3ACB3-E0BC-4370-8DBB-E3720C8C8FD1}
|
||||
{E7EFD64D-6630-4426-B09C-B6862A92E3FD} = {F0CBB7A7-D3FB-41FF-8F47-CF3F6A592249}
|
||||
{F3B04A3A-20C8-4582-A54A-715AF6A5D859} = {8679CAC8-9164-4007-BDD2-F004810EDA14}
|
||||
{8447C956-B03E-4F59-9DD4-877793B849D9} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {615E00ED-BAEF-4E8E-A92A-9B82D87942A9}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<!-- <PublishSingleFile>true</PublishSingleFile> -->
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
||||
<Version>3.1.5.3</Version>
|
||||
<Version>4.0.2.3</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -21,7 +21,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Octokit" Version="0.36.0" />
|
||||
<PackageReference Include="Octokit" Version="0.48.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,10 +2,13 @@ using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using AudibleApi;
|
||||
using FileManager;
|
||||
using InternalUtilities;
|
||||
using LibationWinForms;
|
||||
using LibationWinForms.Dialogs;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serilog;
|
||||
|
||||
@@ -22,6 +25,10 @@ namespace LibationLauncher
|
||||
|
||||
createSettings();
|
||||
|
||||
AudibleApiStorage.EnsureAccountsSettingsFileExists();
|
||||
|
||||
migrate_v3_to_v4();
|
||||
|
||||
ensureLoggingConfig();
|
||||
ensureSerilogConfig();
|
||||
configureLogging();
|
||||
@@ -33,6 +40,11 @@ namespace LibationLauncher
|
||||
|
||||
private static void createSettings()
|
||||
{
|
||||
static bool configSetupIsComplete(Configuration config)
|
||||
=> config.FilesExist
|
||||
&& !string.IsNullOrWhiteSpace(config.DownloadsInProgressEnum)
|
||||
&& !string.IsNullOrWhiteSpace(config.DecryptInProgressEnum);
|
||||
|
||||
var config = Configuration.Instance;
|
||||
if (configSetupIsComplete(config))
|
||||
return;
|
||||
@@ -42,8 +54,6 @@ namespace LibationLauncher
|
||||
var setupDialog = new SetupDialog();
|
||||
setupDialog.NoQuestionsBtn_Click += (_, __) =>
|
||||
{
|
||||
config.DecryptKey ??= "";
|
||||
config.LocaleCountryCode ??= "us";
|
||||
config.DownloadsInProgressEnum ??= "WinTemp";
|
||||
config.DecryptInProgressEnum ??= "WinTemp";
|
||||
config.Books ??= Configuration.AppDir;
|
||||
@@ -70,11 +80,130 @@ namespace LibationLauncher
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
private static bool configSetupIsComplete(Configuration config)
|
||||
=> config.FilesExist
|
||||
&& !string.IsNullOrWhiteSpace(config.LocaleCountryCode)
|
||||
&& !string.IsNullOrWhiteSpace(config.DownloadsInProgressEnum)
|
||||
&& !string.IsNullOrWhiteSpace(config.DecryptInProgressEnum);
|
||||
#region v3 => v4 migration
|
||||
static string AccountsSettingsFileLegacy30 => Path.Combine(Configuration.Instance.LibationFiles, "IdentityTokens.json");
|
||||
|
||||
private static void migrate_v3_to_v4()
|
||||
{
|
||||
migrateLegacyIdentityFile();
|
||||
|
||||
updateSettingsFile();
|
||||
}
|
||||
|
||||
private static void migrateLegacyIdentityFile()
|
||||
{
|
||||
if (File.Exists(AccountsSettingsFileLegacy30))
|
||||
{
|
||||
// don't always rely on applicable POCOs. some is legacy and must be: json file => JObject
|
||||
try
|
||||
{
|
||||
updateLegacyFileWithLocale();
|
||||
|
||||
var account = createAccountFromLegacySettings();
|
||||
account.DecryptKey = getDecryptKey(account);
|
||||
|
||||
// the next few methods need persistence. to be a good citizen, dispose of persister at the end of current scope
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
persister.AccountsSettings.Add(account);
|
||||
}
|
||||
// migration is a convenience. if something goes wrong: just move on
|
||||
catch { }
|
||||
|
||||
// delete legacy token file
|
||||
File.Delete(AccountsSettingsFileLegacy30);
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateLegacyFileWithLocale()
|
||||
{
|
||||
var legacyContents = File.ReadAllText(AccountsSettingsFileLegacy30);
|
||||
var legacyJObj = JObject.Parse(legacyContents);
|
||||
|
||||
// attempt to update legacy token file with locale from settings
|
||||
if (!legacyJObj.ContainsKey("LocaleName"))
|
||||
{
|
||||
var settings = File.ReadAllText(Configuration.Instance.SettingsFilePath);
|
||||
var settingsJObj = JObject.Parse(settings);
|
||||
if (settingsJObj.TryGetValue("LocaleCountryCode", out var localeName))
|
||||
{
|
||||
// update legacy token file with locale from settings
|
||||
legacyJObj.AddFirst(new JProperty("LocaleName", localeName.Value<string>()));
|
||||
|
||||
// save
|
||||
var newContents = legacyJObj.ToString(Formatting.Indented);
|
||||
File.WriteAllText(AccountsSettingsFileLegacy30, newContents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Account createAccountFromLegacySettings()
|
||||
{
|
||||
// get required locale from settings file
|
||||
var settingsContents = File.ReadAllText(Configuration.Instance.SettingsFilePath);
|
||||
if (!JObject.Parse(settingsContents).TryGetValue("LocaleCountryCode", out var jLocale))
|
||||
return null;
|
||||
|
||||
var localeName = jLocale.Value<string>();
|
||||
var locale = Localization.Get(localeName);
|
||||
|
||||
var api = EzApiCreator.GetApiAsync(locale, AccountsSettingsFileLegacy30).GetAwaiter().GetResult();
|
||||
var email = api.GetEmailAsync().GetAwaiter().GetResult();
|
||||
|
||||
// identity has likely been updated above. re-get contents
|
||||
var legacyContents = File.ReadAllText(AccountsSettingsFileLegacy30);
|
||||
|
||||
var identity = AudibleApi.Authorization.Identity.FromJson(legacyContents);
|
||||
|
||||
if (!identity.IsValid)
|
||||
return null;
|
||||
|
||||
var account = new Account(email)
|
||||
{
|
||||
AccountName = $"{email} - {locale.Name}",
|
||||
LibraryScan = true,
|
||||
IdentityTokens = identity
|
||||
};
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
private static string getDecryptKey(Account account)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(account?.DecryptKey))
|
||||
return account.DecryptKey;
|
||||
|
||||
if (!File.Exists(Configuration.Instance.SettingsFilePath) || account is null)
|
||||
return "";
|
||||
|
||||
var settingsContents = File.ReadAllText(Configuration.Instance.SettingsFilePath);
|
||||
if (JObject.Parse(settingsContents).TryGetValue("DecryptKey", out var jToken))
|
||||
return jToken.Value<string>() ?? "";
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private static void updateSettingsFile()
|
||||
{
|
||||
if (!File.Exists(Configuration.Instance.SettingsFilePath))
|
||||
return;
|
||||
|
||||
// use JObject to remove decrypt key and locale from Settings.json
|
||||
var settingsContents = File.ReadAllText(Configuration.Instance.SettingsFilePath);
|
||||
var jObj = JObject.Parse(settingsContents);
|
||||
|
||||
var jLocale = jObj.Property("LocaleCountryCode");
|
||||
var jDecryptKey = jObj.Property("DecryptKey");
|
||||
|
||||
jDecryptKey?.Remove();
|
||||
jLocale?.Remove();
|
||||
|
||||
if (jDecryptKey != null || jLocale != null)
|
||||
{
|
||||
var newContents = jObj.ToString(Formatting.Indented);
|
||||
File.WriteAllText(Configuration.Instance.SettingsFilePath, newContents);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private static string defaultLoggingLevel { get; } = "Information";
|
||||
private static void ensureLoggingConfig()
|
||||
@@ -169,7 +298,7 @@ namespace LibationLauncher
|
||||
|
||||
// CONFIGURATION-DRIVEN (json)
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddJsonFile(config.SettingsJsonPath)
|
||||
.AddJsonFile(config.SettingsFilePath)
|
||||
.Build();
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.ReadFrom.Configuration(configuration)
|
||||
@@ -244,10 +373,26 @@ namespace LibationLauncher
|
||||
|
||||
private static void logStartupState()
|
||||
{
|
||||
Log.Logger.Information("Begin Libation");
|
||||
Log.Logger.Information($"Version: {BuildVersion}");
|
||||
Log.Logger.Information($"LibationFiles: {Configuration.Instance.LibationFiles}");
|
||||
Log.Logger.Information($"Audible locale: {Configuration.Instance.LocaleCountryCode}");
|
||||
var config = Configuration.Instance;
|
||||
|
||||
Log.Logger.Information("Begin Libation. {@DebugInfo}", new
|
||||
{
|
||||
Version = BuildVersion.ToString(),
|
||||
|
||||
config.LibationFiles,
|
||||
AudibleFileStorage.BooksDirectory,
|
||||
|
||||
config.DownloadsInProgressEnum,
|
||||
DownloadsInProgressDir = AudibleFileStorage.DownloadsInProgress,
|
||||
DownloadsInProgressFiles = Directory.EnumerateFiles(AudibleFileStorage.DownloadsInProgress).Count(),
|
||||
|
||||
AudibleFileStorage.DownloadsFinal,
|
||||
DownloadsFinalFiles = Directory.EnumerateFiles(AudibleFileStorage.DownloadsFinal).Count(),
|
||||
|
||||
config.DecryptInProgressEnum,
|
||||
DecryptInProgressDir = AudibleFileStorage.DecryptInProgress,
|
||||
DecryptInProgressFiles = Directory.EnumerateFiles(AudibleFileStorage.DecryptInProgress).Count(),
|
||||
});
|
||||
}
|
||||
|
||||
private static Version BuildVersion => System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||
|
||||
@@ -72,7 +72,12 @@ namespace LibationSearchEngine
|
||||
["CategoriesId"] = lb => lb.Book.CategoriesIds == null ? null : string.Join(", ", lb.Book.CategoriesIds),
|
||||
["CategoryId"] = lb => lb.Book.CategoriesIds == null ? null : string.Join(", ", lb.Book.CategoriesIds),
|
||||
|
||||
[TAGS.FirstCharToUpper()] = lb => lb.Book.UserDefinedItem.Tags
|
||||
[TAGS.FirstCharToUpper()] = lb => lb.Book.UserDefinedItem.Tags,
|
||||
|
||||
["Locale"] = lb => lb.Book.Locale,
|
||||
["Region"] = lb => lb.Book.Locale,
|
||||
["Account"] = lb => lb.Account,
|
||||
["Email"] = lb => lb.Account
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -23,27 +23,25 @@
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="UNTESTED\Dialogs\LibationFilesDialog.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="UNTESTED\Dialogs\LibationFilesDialog.cs" />
|
||||
<Compile Update="UNTESTED\Dialogs\LibationFilesDialog.Designer.cs">
|
||||
<DependentUpon>LibationFilesDialog.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="UNTESTED\Dialogs\SettingsDialog.cs">
|
||||
<Compile Update="UNTESTED\Dialogs\ScanAccountsDialog.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="UNTESTED\Dialogs\ScanAccountsDialog.Designer.cs">
|
||||
<DependentUpon>ScanAccountsDialog.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="UNTESTED\Dialogs\SettingsDialog.cs" />
|
||||
<Compile Update="UNTESTED\Dialogs\SettingsDialog.Designer.cs">
|
||||
<DependentUpon>SettingsDialog.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="UNTESTED\Dialogs\IndexLibraryDialog.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="UNTESTED\Dialogs\IndexLibraryDialog.cs" />
|
||||
<Compile Update="UNTESTED\Dialogs\IndexLibraryDialog.Designer.cs">
|
||||
<DependentUpon>IndexLibraryDialog.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="UNTESTED\Dialogs\SetupDialog.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="UNTESTED\Dialogs\SetupDialog.cs" />
|
||||
<Compile Update="UNTESTED\Dialogs\SetupDialog.Designer.cs">
|
||||
<DependentUpon>SetupDialog.cs</DependentUpon>
|
||||
</Compile>
|
||||
@@ -57,6 +55,9 @@
|
||||
<EmbeddedResource Update="UNTESTED\Dialogs\LibationFilesDialog.resx">
|
||||
<DependentUpon>LibationFilesDialog.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="UNTESTED\Dialogs\ScanAccountsDialog.resx">
|
||||
<DependentUpon>ScanAccountsDialog.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="UNTESTED\Dialogs\SettingsDialog.resx">
|
||||
<DependentUpon>SettingsDialog.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
|
||||
146
LibationWinForms/UNTESTED/Dialogs/AccountsDialog.Designer.cs
generated
Normal file
146
LibationWinForms/UNTESTED/Dialogs/AccountsDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,146 @@
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
partial class AccountsDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.cancelBtn = new System.Windows.Forms.Button();
|
||||
this.saveBtn = new System.Windows.Forms.Button();
|
||||
this.dataGridView1 = new System.Windows.Forms.DataGridView();
|
||||
this.DeleteAccount = new System.Windows.Forms.DataGridViewButtonColumn();
|
||||
this.LibraryScan = new System.Windows.Forms.DataGridViewCheckBoxColumn();
|
||||
this.AccountId = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.Locale = new System.Windows.Forms.DataGridViewComboBoxColumn();
|
||||
this.AccountName = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// cancelBtn
|
||||
//
|
||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.cancelBtn.Location = new System.Drawing.Point(713, 415);
|
||||
this.cancelBtn.Name = "cancelBtn";
|
||||
this.cancelBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.cancelBtn.TabIndex = 2;
|
||||
this.cancelBtn.Text = "Cancel";
|
||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
|
||||
//
|
||||
// saveBtn
|
||||
//
|
||||
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.saveBtn.Location = new System.Drawing.Point(612, 415);
|
||||
this.saveBtn.Name = "saveBtn";
|
||||
this.saveBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.saveBtn.TabIndex = 1;
|
||||
this.saveBtn.Text = "Save";
|
||||
this.saveBtn.UseVisualStyleBackColor = true;
|
||||
this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click);
|
||||
//
|
||||
// dataGridView1
|
||||
//
|
||||
this.dataGridView1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.dataGridView1.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells;
|
||||
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||
this.DeleteAccount,
|
||||
this.LibraryScan,
|
||||
this.AccountId,
|
||||
this.Locale,
|
||||
this.AccountName});
|
||||
this.dataGridView1.Location = new System.Drawing.Point(12, 12);
|
||||
this.dataGridView1.MultiSelect = false;
|
||||
this.dataGridView1.Name = "dataGridView1";
|
||||
this.dataGridView1.Size = new System.Drawing.Size(776, 397);
|
||||
this.dataGridView1.TabIndex = 0;
|
||||
this.dataGridView1.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.DataGridView1_CellContentClick);
|
||||
this.dataGridView1.DefaultValuesNeeded += new System.Windows.Forms.DataGridViewRowEventHandler(this.dataGridView1_DefaultValuesNeeded);
|
||||
//
|
||||
// DeleteAccount
|
||||
//
|
||||
this.DeleteAccount.HeaderText = "Delete";
|
||||
this.DeleteAccount.Name = "DeleteAccount";
|
||||
this.DeleteAccount.ReadOnly = true;
|
||||
this.DeleteAccount.Text = "x";
|
||||
this.DeleteAccount.Width = 44;
|
||||
//
|
||||
// LibraryScan
|
||||
//
|
||||
this.LibraryScan.HeaderText = "Include in library scan?";
|
||||
this.LibraryScan.Name = "LibraryScan";
|
||||
this.LibraryScan.Width = 83;
|
||||
//
|
||||
// AccountId
|
||||
//
|
||||
this.AccountId.HeaderText = "Audible email/login";
|
||||
this.AccountId.Name = "AccountId";
|
||||
this.AccountId.Width = 111;
|
||||
//
|
||||
// Locale
|
||||
//
|
||||
this.Locale.HeaderText = "Locale";
|
||||
this.Locale.Name = "Locale";
|
||||
this.Locale.Width = 45;
|
||||
//
|
||||
// AccountName
|
||||
//
|
||||
this.AccountName.HeaderText = "Account nickname (optional)";
|
||||
this.AccountName.Name = "AccountName";
|
||||
this.AccountName.Width = 152;
|
||||
//
|
||||
// AccountsDialog
|
||||
//
|
||||
this.AcceptButton = this.saveBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.cancelBtn;
|
||||
this.ClientSize = new System.Drawing.Size(800, 450);
|
||||
this.Controls.Add(this.dataGridView1);
|
||||
this.Controls.Add(this.saveBtn);
|
||||
this.Controls.Add(this.cancelBtn);
|
||||
this.Name = "AccountsDialog";
|
||||
this.Text = "Audible Accounts";
|
||||
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Button cancelBtn;
|
||||
private System.Windows.Forms.Button saveBtn;
|
||||
private System.Windows.Forms.DataGridView dataGridView1;
|
||||
private System.Windows.Forms.DataGridViewButtonColumn DeleteAccount;
|
||||
private System.Windows.Forms.DataGridViewCheckBoxColumn LibraryScan;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn AccountId;
|
||||
private System.Windows.Forms.DataGridViewComboBoxColumn Locale;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn AccountName;
|
||||
}
|
||||
}
|
||||
178
LibationWinForms/UNTESTED/Dialogs/AccountsDialog.cs
Normal file
178
LibationWinForms/UNTESTED/Dialogs/AccountsDialog.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using AudibleApi;
|
||||
using InternalUtilities;
|
||||
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
public partial class AccountsDialog : Form
|
||||
{
|
||||
const string COL_Delete = nameof(DeleteAccount);
|
||||
const string COL_LibraryScan = nameof(LibraryScan);
|
||||
const string COL_AccountId = nameof(AccountId);
|
||||
const string COL_AccountName = nameof(AccountName);
|
||||
const string COL_Locale = nameof(Locale);
|
||||
|
||||
Form1 _parent { get; }
|
||||
|
||||
public AccountsDialog(Form1 parent)
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
dataGridView1.Columns[COL_AccountName].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
||||
|
||||
populateDropDown();
|
||||
|
||||
populateGridValues();
|
||||
}
|
||||
|
||||
private void populateDropDown()
|
||||
=> (dataGridView1.Columns[COL_Locale] as DataGridViewComboBoxColumn).DataSource
|
||||
= Localization.Locales
|
||||
.Select(l => l.Name)
|
||||
.OrderBy(a => a).ToList();
|
||||
|
||||
private void populateGridValues()
|
||||
{
|
||||
// WARNING: accounts persister will write ANY EDIT to object immediately to file
|
||||
// here: copy strings and dispose of persister
|
||||
// only persist in 'save' step
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var accounts = persister.AccountsSettings.Accounts;
|
||||
if (!accounts.Any())
|
||||
return;
|
||||
|
||||
foreach (var account in accounts)
|
||||
dataGridView1.Rows.Add(
|
||||
"X",
|
||||
account.LibraryScan,
|
||||
account.AccountId,
|
||||
account.Locale.Name,
|
||||
account.AccountName);
|
||||
}
|
||||
|
||||
private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
|
||||
{
|
||||
e.Row.Cells[COL_Delete].Value = "X";
|
||||
e.Row.Cells[COL_LibraryScan].Value = true;
|
||||
}
|
||||
|
||||
private void DataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
|
||||
{
|
||||
var dgv = (DataGridView)sender;
|
||||
|
||||
var col = dgv.Columns[e.ColumnIndex];
|
||||
if (col is DataGridViewButtonColumn && e.RowIndex >= 0)
|
||||
{
|
||||
var row = dgv.Rows[e.RowIndex];
|
||||
switch (col.Name)
|
||||
{
|
||||
case COL_Delete:
|
||||
// if final/edit row: do nothing
|
||||
if (e.RowIndex < dgv.RowCount - 1)
|
||||
dgv.Rows.Remove(row);
|
||||
break;
|
||||
//case COL_MoveUp:
|
||||
// // if top: do nothing
|
||||
// if (e.RowIndex < 1)
|
||||
// break;
|
||||
// dgv.Rows.Remove(row);
|
||||
// dgv.Rows.Insert(e.RowIndex - 1, row);
|
||||
// break;
|
||||
//case COL_MoveDown:
|
||||
// // if final/edit row or bottom filter row: do nothing
|
||||
// if (e.RowIndex >= dgv.RowCount - 2)
|
||||
// break;
|
||||
// dgv.Rows.Remove(row);
|
||||
// dgv.Rows.Insert(e.RowIndex + 1, row);
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelBtn_Click(object sender, EventArgs e) => this.Close();
|
||||
|
||||
class AccountDto
|
||||
{
|
||||
public string AccountId { get; set; }
|
||||
public string AccountName { get; set; }
|
||||
public string LocaleName { get; set; }
|
||||
public bool LibraryScan { get; set; }
|
||||
}
|
||||
|
||||
private void saveBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// without transaction, accounts persister will write ANY EDIT immediately to file
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
|
||||
persister.BeginTransation();
|
||||
persist(persister.AccountsSettings);
|
||||
persister.CommitTransation();
|
||||
|
||||
_parent.RefreshImportMenu();
|
||||
this.DialogResult = DialogResult.OK;
|
||||
this.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Error: {ex.Message}", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void persist(AccountsSettings accountsSettings)
|
||||
{
|
||||
var existingAccounts = accountsSettings.Accounts;
|
||||
var dtos = getRowDtos();
|
||||
|
||||
// editing account id is a special case. an account is defined by its account id, therefore this is really a different account. the user won't care about this distinction though.
|
||||
// these will be caught below by normal means and re-created minus the convenience of persisting identity tokens
|
||||
|
||||
// delete
|
||||
for (var i = existingAccounts.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var existing = existingAccounts[i];
|
||||
if (!dtos.Any(dto =>
|
||||
dto.AccountId?.ToLower().Trim() == existing.AccountId.ToLower()
|
||||
&& dto.LocaleName == existing.Locale?.Name))
|
||||
{
|
||||
accountsSettings.Delete(existing);
|
||||
}
|
||||
}
|
||||
|
||||
// upsert each. validation occurs through Account and AccountsSettings
|
||||
foreach (var dto in dtos)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dto.AccountId))
|
||||
throw new Exception("Please enter an account id for all accounts");
|
||||
if (string.IsNullOrWhiteSpace(dto.LocaleName))
|
||||
throw new Exception("Please select a local name for all accounts");
|
||||
|
||||
var acct = accountsSettings.Upsert(dto.AccountId, dto.LocaleName);
|
||||
acct.LibraryScan = dto.LibraryScan;
|
||||
acct.AccountName
|
||||
= string.IsNullOrWhiteSpace(dto.AccountName)
|
||||
? $"{dto.AccountId} - {dto.LocaleName}"
|
||||
: dto.AccountName.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
private List<AccountDto> getRowDtos()
|
||||
=> dataGridView1.Rows
|
||||
.Cast<DataGridViewRow>()
|
||||
.Where(r => !r.IsNewRow)
|
||||
.Select(r => new AccountDto
|
||||
{
|
||||
AccountId = (string)r.Cells[COL_AccountId].Value,
|
||||
AccountName = (string)r.Cells[COL_AccountName].Value,
|
||||
LocaleName = (string)r.Cells[COL_Locale].Value,
|
||||
LibraryScan = (bool)r.Cells[COL_LibraryScan].Value
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
138
LibationWinForms/UNTESTED/Dialogs/AccountsDialog.resx
Normal file
138
LibationWinForms/UNTESTED/Dialogs/AccountsDialog.resx
Normal file
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="Original.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="DeleteAccount.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="LibraryScan.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="AccountId.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="AccountName.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="Locale.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
</root>
|
||||
@@ -49,7 +49,7 @@
|
||||
this.cancelBtn.TabIndex = 2;
|
||||
this.cancelBtn.Text = "Cancel";
|
||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||
this.cancelBtn.Click += new System.EventHandler(this.CancelBtn_Click);
|
||||
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
|
||||
//
|
||||
// saveBtn
|
||||
//
|
||||
@@ -60,7 +60,7 @@
|
||||
this.saveBtn.TabIndex = 1;
|
||||
this.saveBtn.Text = "Save";
|
||||
this.saveBtn.UseVisualStyleBackColor = true;
|
||||
this.saveBtn.Click += new System.EventHandler(this.SaveBtn_Click);
|
||||
this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click);
|
||||
//
|
||||
// dataGridView1
|
||||
//
|
||||
@@ -81,6 +81,7 @@
|
||||
this.dataGridView1.Size = new System.Drawing.Size(776, 397);
|
||||
this.dataGridView1.TabIndex = 0;
|
||||
this.dataGridView1.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.DataGridView1_CellContentClick);
|
||||
this.dataGridView1.DefaultValuesNeeded += new System.Windows.Forms.DataGridViewRowEventHandler(this.dataGridView1_DefaultValuesNeeded);
|
||||
//
|
||||
// Original
|
||||
//
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using FileManager;
|
||||
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
public partial class EditQuickFilters : Form
|
||||
public partial class EditQuickFilters : Form
|
||||
{
|
||||
const string COL_Original = "Original";
|
||||
const string COL_Delete = "Delete";
|
||||
const string COL_Filter = "Filter";
|
||||
const string COL_MoveUp = "MoveUp";
|
||||
const string COL_MoveDown = "MoveDown";
|
||||
const string BLACK_UP_POINTING_TRIANGLE = "\u25B2";
|
||||
const string BLACK_DOWN_POINTING_TRIANGLE = "\u25BC";
|
||||
|
||||
const string COL_Original = nameof(Original);
|
||||
const string COL_Delete = nameof(Delete);
|
||||
const string COL_Filter = nameof(Filter);
|
||||
const string COL_MoveUp = nameof(MoveUp);
|
||||
const string COL_MoveDown = nameof(MoveDown);
|
||||
|
||||
Form1 _parent { get; }
|
||||
|
||||
@@ -29,20 +26,27 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
dataGridView1.Columns[COL_Filter].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
||||
|
||||
populateFilters();
|
||||
populateGridValues();
|
||||
}
|
||||
|
||||
private void populateFilters()
|
||||
private void populateGridValues()
|
||||
{
|
||||
var filters = QuickFilters.Filters;
|
||||
if (!filters.Any())
|
||||
return;
|
||||
|
||||
foreach (var filter in filters)
|
||||
dataGridView1.Rows.Add(filter, "X", filter, "\u25B2", "\u25BC");
|
||||
dataGridView1.Rows.Add(filter, "X", filter, BLACK_UP_POINTING_TRIANGLE, BLACK_DOWN_POINTING_TRIANGLE);
|
||||
}
|
||||
|
||||
private void SaveBtn_Click(object sender, EventArgs e)
|
||||
private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
|
||||
{
|
||||
e.Row.Cells[COL_Delete].Value = "X";
|
||||
e.Row.Cells[COL_MoveUp].Value = BLACK_UP_POINTING_TRIANGLE;
|
||||
e.Row.Cells[COL_MoveDown].Value = BLACK_DOWN_POINTING_TRIANGLE;
|
||||
}
|
||||
|
||||
private void saveBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
var list = dataGridView1.Rows
|
||||
.OfType<DataGridViewRow>()
|
||||
@@ -51,10 +55,11 @@ namespace LibationWinForms.Dialogs
|
||||
QuickFilters.ReplaceAll(list);
|
||||
|
||||
_parent.UpdateFilterDropDown();
|
||||
this.DialogResult = DialogResult.OK;
|
||||
this.Close();
|
||||
}
|
||||
|
||||
private void CancelBtn_Click(object sender, EventArgs e) => this.Close();
|
||||
private void cancelBtn_Click(object sender, EventArgs e) => this.Close();
|
||||
|
||||
private void DataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
|
||||
{
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Location = new System.Drawing.Point(28, 24);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(260, 13);
|
||||
this.label1.Size = new System.Drawing.Size(263, 13);
|
||||
this.label1.TabIndex = 0;
|
||||
this.label1.Text = "Scanning Audible library. This may take a few minutes";
|
||||
//
|
||||
@@ -44,7 +44,7 @@
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(319, 63);
|
||||
this.ClientSize = new System.Drawing.Size(440, 63);
|
||||
this.Controls.Add(this.label1);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.MaximizeBox = false;
|
||||
|
||||
@@ -1,31 +1,44 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using ApplicationServices;
|
||||
using InternalUtilities;
|
||||
using LibationWinForms.Login;
|
||||
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
public partial class IndexLibraryDialog : Form
|
||||
{
|
||||
private Account[] _accounts { get; }
|
||||
|
||||
public int NewBooksAdded { get; private set; }
|
||||
public int TotalBooksProcessed { get; private set; }
|
||||
|
||||
public IndexLibraryDialog()
|
||||
public IndexLibraryDialog(params Account[] accounts)
|
||||
{
|
||||
_accounts = accounts;
|
||||
InitializeComponent();
|
||||
this.Shown += IndexLibraryDialog_Shown;
|
||||
}
|
||||
|
||||
private async void IndexLibraryDialog_Shown(object sender, System.EventArgs e)
|
||||
private async void IndexLibraryDialog_Shown(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
if (_accounts != null && _accounts.Length > 0)
|
||||
{
|
||||
(TotalBooksProcessed, NewBooksAdded) = await LibraryCommands.ImportLibraryAsync(new WinformResponder());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = "Error importing library. Please try again. If this still happens after 2 or 3 tries, stop and contact administrator";
|
||||
MessageBox.Show(msg, "Error importing library", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
this.label1.Text
|
||||
= (_accounts.Length == 1)
|
||||
? "Scanning Audible library. This may take a few minutes."
|
||||
: $"Scanning Audible library: {_accounts.Length} accounts. This may take a few minutes per account.";
|
||||
|
||||
try
|
||||
{
|
||||
(TotalBooksProcessed, NewBooksAdded) = await LibraryCommands.ImportAccountAsync((account) => new WinformResponder(account), _accounts);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = "Error importing library. Please try again. If this still happens after 2 or 3 tries, stop and contact administrator";
|
||||
Serilog.Log.Logger.Error(ex, msg);
|
||||
MessageBox.Show(msg, "Error importing library", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
this.Close();
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.passwordLbl = new System.Windows.Forms.Label();
|
||||
this.emailLbl = new System.Windows.Forms.Label();
|
||||
this.passwordTb = new System.Windows.Forms.TextBox();
|
||||
this.emailTb = new System.Windows.Forms.TextBox();
|
||||
this.submitBtn = new System.Windows.Forms.Button();
|
||||
this.localeLbl = new System.Windows.Forms.Label();
|
||||
this.usernameLbl = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// passwordLbl
|
||||
@@ -44,15 +44,6 @@
|
||||
this.passwordLbl.TabIndex = 2;
|
||||
this.passwordLbl.Text = "Password";
|
||||
//
|
||||
// emailLbl
|
||||
//
|
||||
this.emailLbl.AutoSize = true;
|
||||
this.emailLbl.Location = new System.Drawing.Point(12, 15);
|
||||
this.emailLbl.Name = "emailLbl";
|
||||
this.emailLbl.Size = new System.Drawing.Size(32, 13);
|
||||
this.emailLbl.TabIndex = 0;
|
||||
this.emailLbl.Text = "Email";
|
||||
//
|
||||
// passwordTb
|
||||
//
|
||||
this.passwordTb.Location = new System.Drawing.Point(71, 38);
|
||||
@@ -61,13 +52,6 @@
|
||||
this.passwordTb.Size = new System.Drawing.Size(200, 20);
|
||||
this.passwordTb.TabIndex = 3;
|
||||
//
|
||||
// emailTb
|
||||
//
|
||||
this.emailTb.Location = new System.Drawing.Point(71, 12);
|
||||
this.emailTb.Name = "emailTb";
|
||||
this.emailTb.Size = new System.Drawing.Size(200, 20);
|
||||
this.emailTb.TabIndex = 1;
|
||||
//
|
||||
// submitBtn
|
||||
//
|
||||
this.submitBtn.Location = new System.Drawing.Point(196, 64);
|
||||
@@ -78,17 +62,35 @@
|
||||
this.submitBtn.UseVisualStyleBackColor = true;
|
||||
this.submitBtn.Click += new System.EventHandler(this.submitBtn_Click);
|
||||
//
|
||||
// localeLbl
|
||||
//
|
||||
this.localeLbl.AutoSize = true;
|
||||
this.localeLbl.Location = new System.Drawing.Point(12, 9);
|
||||
this.localeLbl.Name = "localeLbl";
|
||||
this.localeLbl.Size = new System.Drawing.Size(59, 13);
|
||||
this.localeLbl.TabIndex = 0;
|
||||
this.localeLbl.Text = "Locale: {0}";
|
||||
//
|
||||
// usernameLbl
|
||||
//
|
||||
this.usernameLbl.AutoSize = true;
|
||||
this.usernameLbl.Location = new System.Drawing.Point(12, 22);
|
||||
this.usernameLbl.Name = "usernameLbl";
|
||||
this.usernameLbl.Size = new System.Drawing.Size(75, 13);
|
||||
this.usernameLbl.TabIndex = 1;
|
||||
this.usernameLbl.Text = "Username: {0}";
|
||||
//
|
||||
// AudibleLoginDialog
|
||||
//
|
||||
this.AcceptButton = this.submitBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(283, 99);
|
||||
this.Controls.Add(this.usernameLbl);
|
||||
this.Controls.Add(this.localeLbl);
|
||||
this.Controls.Add(this.submitBtn);
|
||||
this.Controls.Add(this.passwordLbl);
|
||||
this.Controls.Add(this.emailLbl);
|
||||
this.Controls.Add(this.passwordTb);
|
||||
this.Controls.Add(this.emailTb);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
@@ -104,9 +106,9 @@
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label passwordLbl;
|
||||
private System.Windows.Forms.Label emailLbl;
|
||||
private System.Windows.Forms.TextBox passwordTb;
|
||||
private System.Windows.Forms.TextBox emailTb;
|
||||
private System.Windows.Forms.Button submitBtn;
|
||||
private System.Windows.Forms.Label localeLbl;
|
||||
private System.Windows.Forms.Label usernameLbl;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,36 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using InternalUtilities;
|
||||
|
||||
namespace LibationWinForms.Dialogs.Login
|
||||
{
|
||||
public partial class AudibleLoginDialog : Form
|
||||
{
|
||||
private string locale { get; }
|
||||
private string accountId { get; }
|
||||
|
||||
public string Email { get; private set; }
|
||||
public string Password { get; private set; }
|
||||
|
||||
public AudibleLoginDialog()
|
||||
public AudibleLoginDialog(Account account)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
locale = account.Locale.Name;
|
||||
accountId = account.AccountId;
|
||||
|
||||
// do not allow user to change login id here. if they do then jsonpath will fail
|
||||
this.localeLbl.Text = string.Format(this.localeLbl.Text, locale);
|
||||
this.usernameLbl.Text = string.Format(this.usernameLbl.Text, accountId);
|
||||
}
|
||||
|
||||
private void submitBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
Email = this.emailTb.Text;
|
||||
Email = accountId;
|
||||
Password = this.passwordTb.Text;
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
// Close() not needed for AcceptButton
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ namespace LibationWinForms.Dialogs.Login
|
||||
private void submitBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
Answer = this.answerTb.Text;
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
// Close() not needed for AcceptButton
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,19 @@
|
||||
using System;
|
||||
using AudibleApi;
|
||||
using InternalUtilities;
|
||||
using LibationWinForms.Dialogs.Login;
|
||||
|
||||
namespace LibationWinForms.Login
|
||||
{
|
||||
public class WinformResponder : AudibleApi.ILoginCallback
|
||||
public class WinformResponder : ILoginCallback
|
||||
{
|
||||
private Account _account { get; }
|
||||
|
||||
public WinformResponder(Account account)
|
||||
{
|
||||
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
|
||||
}
|
||||
|
||||
public string Get2faCode()
|
||||
{
|
||||
using var dialog = new _2faCodeDialog();
|
||||
@@ -23,7 +32,7 @@ namespace LibationWinForms.Login
|
||||
|
||||
public (string email, string password) GetLogin()
|
||||
{
|
||||
using var dialog = new AudibleLoginDialog();
|
||||
using var dialog = new AudibleLoginDialog(_account);
|
||||
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
|
||||
return (dialog.Email, dialog.Password);
|
||||
return (null, null);
|
||||
|
||||
121
LibationWinForms/UNTESTED/Dialogs/ScanAccountsDialog.Designer.cs
generated
Normal file
121
LibationWinForms/UNTESTED/Dialogs/ScanAccountsDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,121 @@
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
partial class ScanAccountsDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.accountsLbl = new System.Windows.Forms.Label();
|
||||
this.accountsClb = new System.Windows.Forms.CheckedListBox();
|
||||
this.importBtn = new System.Windows.Forms.Button();
|
||||
this.cancelBtn = new System.Windows.Forms.Button();
|
||||
this.editBtn = new System.Windows.Forms.Button();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// accountsLbl
|
||||
//
|
||||
this.accountsLbl.AutoSize = true;
|
||||
this.accountsLbl.Location = new System.Drawing.Point(12, 9);
|
||||
this.accountsLbl.Name = "accountsLbl";
|
||||
this.accountsLbl.Size = new System.Drawing.Size(467, 13);
|
||||
this.accountsLbl.TabIndex = 0;
|
||||
this.accountsLbl.Text = "Check the accounts to scan and import. To change default selections, go to: Setti" +
|
||||
"ngs > Accounts";
|
||||
//
|
||||
// accountsClb
|
||||
//
|
||||
this.accountsClb.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.accountsClb.FormattingEnabled = true;
|
||||
this.accountsClb.Location = new System.Drawing.Point(12, 25);
|
||||
this.accountsClb.Name = "accountsClb";
|
||||
this.accountsClb.Size = new System.Drawing.Size(560, 94);
|
||||
this.accountsClb.TabIndex = 1;
|
||||
//
|
||||
// importBtn
|
||||
//
|
||||
this.importBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.importBtn.Location = new System.Drawing.Point(396, 125);
|
||||
this.importBtn.Name = "importBtn";
|
||||
this.importBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.importBtn.TabIndex = 3;
|
||||
this.importBtn.Text = "Import";
|
||||
this.importBtn.UseVisualStyleBackColor = true;
|
||||
this.importBtn.Click += new System.EventHandler(this.importBtn_Click);
|
||||
//
|
||||
// cancelBtn
|
||||
//
|
||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.cancelBtn.Location = new System.Drawing.Point(497, 125);
|
||||
this.cancelBtn.Name = "cancelBtn";
|
||||
this.cancelBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.cancelBtn.TabIndex = 4;
|
||||
this.cancelBtn.Text = "Cancel";
|
||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
|
||||
//
|
||||
// editBtn
|
||||
//
|
||||
this.editBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.editBtn.Location = new System.Drawing.Point(12, 125);
|
||||
this.editBtn.Name = "editBtn";
|
||||
this.editBtn.Size = new System.Drawing.Size(90, 23);
|
||||
this.editBtn.TabIndex = 2;
|
||||
this.editBtn.Text = "Edit accounts";
|
||||
this.editBtn.UseVisualStyleBackColor = true;
|
||||
this.editBtn.Click += new System.EventHandler(this.editBtn_Click);
|
||||
//
|
||||
// ScanAccountsDialog
|
||||
//
|
||||
this.AcceptButton = this.importBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.cancelBtn;
|
||||
this.ClientSize = new System.Drawing.Size(584, 160);
|
||||
this.Controls.Add(this.editBtn);
|
||||
this.Controls.Add(this.cancelBtn);
|
||||
this.Controls.Add(this.importBtn);
|
||||
this.Controls.Add(this.accountsClb);
|
||||
this.Controls.Add(this.accountsLbl);
|
||||
this.Name = "ScanAccountsDialog";
|
||||
this.Text = "Which accounts?";
|
||||
this.Load += new System.EventHandler(this.ScanAccountsDialog_Load);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label accountsLbl;
|
||||
private System.Windows.Forms.CheckedListBox accountsClb;
|
||||
private System.Windows.Forms.Button importBtn;
|
||||
private System.Windows.Forms.Button cancelBtn;
|
||||
private System.Windows.Forms.Button editBtn;
|
||||
}
|
||||
}
|
||||
69
LibationWinForms/UNTESTED/Dialogs/ScanAccountsDialog.cs
Normal file
69
LibationWinForms/UNTESTED/Dialogs/ScanAccountsDialog.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using InternalUtilities;
|
||||
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
public partial class ScanAccountsDialog : Form
|
||||
{
|
||||
public List<Account> CheckedAccounts { get; } = new List<Account>();
|
||||
|
||||
Form1 _parent { get; }
|
||||
|
||||
public ScanAccountsDialog(Form1 parent)
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
class listItem
|
||||
{
|
||||
public Account Account { get; set; }
|
||||
public string Text { get; set; }
|
||||
public override string ToString() => Text;
|
||||
}
|
||||
private void ScanAccountsDialog_Load(object sender, EventArgs e)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var accounts = persister.AccountsSettings.Accounts;
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
var item = new listItem
|
||||
{
|
||||
Account = account,
|
||||
Text = $"{account.AccountName} ({account.AccountId} - {account.Locale.Name})"
|
||||
};
|
||||
this.accountsClb.Items.Add(item, account.LibraryScan);
|
||||
}
|
||||
}
|
||||
|
||||
private void editBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (new AccountsDialog(_parent).ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
// clear grid
|
||||
this.accountsClb.Items.Clear();
|
||||
|
||||
// reload grid and default checkboxes
|
||||
ScanAccountsDialog_Load(sender, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void importBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
foreach (listItem item in accountsClb.CheckedItems)
|
||||
CheckedAccounts.Add(item.Account);
|
||||
|
||||
this.DialogResult = DialogResult.OK;
|
||||
this.Close();
|
||||
}
|
||||
|
||||
private void cancelBtn_Click(object sender, EventArgs e) => this.Close();
|
||||
}
|
||||
}
|
||||
120
LibationWinForms/UNTESTED/Dialogs/ScanAccountsDialog.resx
Normal file
120
LibationWinForms/UNTESTED/Dialogs/ScanAccountsDialog.resx
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -89,7 +89,7 @@
|
||||
//
|
||||
this.closeBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.closeBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.closeBtn.Location = new System.Drawing.Point(890, 415);
|
||||
this.closeBtn.Location = new System.Drawing.Point(890, 465);
|
||||
this.closeBtn.Name = "closeBtn";
|
||||
this.closeBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.closeBtn.TabIndex = 5;
|
||||
@@ -103,7 +103,7 @@
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.closeBtn;
|
||||
this.ClientSize = new System.Drawing.Size(977, 450);
|
||||
this.ClientSize = new System.Drawing.Size(977, 500);
|
||||
this.Controls.Add(this.closeBtn);
|
||||
this.Controls.Add(this.label5);
|
||||
this.Controls.Add(this.label4);
|
||||
|
||||
@@ -28,12 +28,9 @@
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.decryptKeyLbl = new System.Windows.Forms.Label();
|
||||
this.decryptKeyTb = new System.Windows.Forms.TextBox();
|
||||
this.booksLocationLbl = new System.Windows.Forms.Label();
|
||||
this.booksLocationTb = new System.Windows.Forms.TextBox();
|
||||
this.booksLocationSearchBtn = new System.Windows.Forms.Button();
|
||||
this.decryptKeyDescLbl = new System.Windows.Forms.Label();
|
||||
this.booksLocationDescLbl = new System.Windows.Forms.Label();
|
||||
this.downloadsInProgressGb = new System.Windows.Forms.GroupBox();
|
||||
this.downloadsInProgressLibationFilesRb = new System.Windows.Forms.RadioButton();
|
||||
@@ -45,30 +42,12 @@
|
||||
this.decryptInProgressDescLbl = new System.Windows.Forms.Label();
|
||||
this.saveBtn = new System.Windows.Forms.Button();
|
||||
this.cancelBtn = new System.Windows.Forms.Button();
|
||||
this.audibleLocaleLbl = new System.Windows.Forms.Label();
|
||||
this.audibleLocaleCb = new System.Windows.Forms.ComboBox();
|
||||
this.groupBox1 = new System.Windows.Forms.GroupBox();
|
||||
this.downloadsInProgressGb.SuspendLayout();
|
||||
this.decryptInProgressGb.SuspendLayout();
|
||||
this.groupBox1.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// decryptKeyLbl
|
||||
//
|
||||
this.decryptKeyLbl.AutoSize = true;
|
||||
this.decryptKeyLbl.Location = new System.Drawing.Point(6, 22);
|
||||
this.decryptKeyLbl.Name = "decryptKeyLbl";
|
||||
this.decryptKeyLbl.Size = new System.Drawing.Size(64, 13);
|
||||
this.decryptKeyLbl.TabIndex = 0;
|
||||
this.decryptKeyLbl.Text = "Decrypt key";
|
||||
//
|
||||
// decryptKeyTb
|
||||
//
|
||||
this.decryptKeyTb.Location = new System.Drawing.Point(76, 19);
|
||||
this.decryptKeyTb.Name = "decryptKeyTb";
|
||||
this.decryptKeyTb.Size = new System.Drawing.Size(100, 20);
|
||||
this.decryptKeyTb.TabIndex = 1;
|
||||
//
|
||||
// booksLocationLbl
|
||||
//
|
||||
this.booksLocationLbl.AutoSize = true;
|
||||
@@ -95,15 +74,6 @@
|
||||
this.booksLocationSearchBtn.UseVisualStyleBackColor = true;
|
||||
this.booksLocationSearchBtn.Click += new System.EventHandler(this.booksLocationSearchBtn_Click);
|
||||
//
|
||||
// decryptKeyDescLbl
|
||||
//
|
||||
this.decryptKeyDescLbl.AutoSize = true;
|
||||
this.decryptKeyDescLbl.Location = new System.Drawing.Point(73, 42);
|
||||
this.decryptKeyDescLbl.Name = "decryptKeyDescLbl";
|
||||
this.decryptKeyDescLbl.Size = new System.Drawing.Size(36, 13);
|
||||
this.decryptKeyDescLbl.TabIndex = 2;
|
||||
this.decryptKeyDescLbl.Text = "[desc]";
|
||||
//
|
||||
// booksLocationDescLbl
|
||||
//
|
||||
this.booksLocationDescLbl.AutoSize = true;
|
||||
@@ -118,7 +88,7 @@
|
||||
this.downloadsInProgressGb.Controls.Add(this.downloadsInProgressLibationFilesRb);
|
||||
this.downloadsInProgressGb.Controls.Add(this.downloadsInProgressWinTempRb);
|
||||
this.downloadsInProgressGb.Controls.Add(this.downloadsInProgressDescLbl);
|
||||
this.downloadsInProgressGb.Location = new System.Drawing.Point(15, 58);
|
||||
this.downloadsInProgressGb.Location = new System.Drawing.Point(15, 19);
|
||||
this.downloadsInProgressGb.Name = "downloadsInProgressGb";
|
||||
this.downloadsInProgressGb.Size = new System.Drawing.Size(758, 117);
|
||||
this.downloadsInProgressGb.TabIndex = 4;
|
||||
@@ -163,7 +133,7 @@
|
||||
this.decryptInProgressGb.Controls.Add(this.decryptInProgressLibationFilesRb);
|
||||
this.decryptInProgressGb.Controls.Add(this.decryptInProgressWinTempRb);
|
||||
this.decryptInProgressGb.Controls.Add(this.decryptInProgressDescLbl);
|
||||
this.decryptInProgressGb.Location = new System.Drawing.Point(9, 183);
|
||||
this.decryptInProgressGb.Location = new System.Drawing.Point(9, 144);
|
||||
this.decryptInProgressGb.Name = "decryptInProgressGb";
|
||||
this.decryptInProgressGb.Size = new System.Drawing.Size(758, 117);
|
||||
this.decryptInProgressGb.TabIndex = 5;
|
||||
@@ -206,7 +176,7 @@
|
||||
// saveBtn
|
||||
//
|
||||
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.saveBtn.Location = new System.Drawing.Point(612, 404);
|
||||
this.saveBtn.Location = new System.Drawing.Point(612, 328);
|
||||
this.saveBtn.Name = "saveBtn";
|
||||
this.saveBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.saveBtn.TabIndex = 7;
|
||||
@@ -218,7 +188,7 @@
|
||||
//
|
||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.cancelBtn.Location = new System.Drawing.Point(713, 404);
|
||||
this.cancelBtn.Location = new System.Drawing.Point(713, 328);
|
||||
this.cancelBtn.Name = "cancelBtn";
|
||||
this.cancelBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.cancelBtn.TabIndex = 8;
|
||||
@@ -226,40 +196,13 @@
|
||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
|
||||
//
|
||||
// audibleLocaleLbl
|
||||
//
|
||||
this.audibleLocaleLbl.AutoSize = true;
|
||||
this.audibleLocaleLbl.Location = new System.Drawing.Point(12, 56);
|
||||
this.audibleLocaleLbl.Name = "audibleLocaleLbl";
|
||||
this.audibleLocaleLbl.Size = new System.Drawing.Size(77, 13);
|
||||
this.audibleLocaleLbl.TabIndex = 4;
|
||||
this.audibleLocaleLbl.Text = "Audible Locale";
|
||||
//
|
||||
// audibleLocaleCb
|
||||
//
|
||||
this.audibleLocaleCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.audibleLocaleCb.FormattingEnabled = true;
|
||||
this.audibleLocaleCb.Items.AddRange(new object[] {
|
||||
"us",
|
||||
"uk",
|
||||
"germany",
|
||||
"france",
|
||||
"canada"});
|
||||
this.audibleLocaleCb.Location = new System.Drawing.Point(95, 53);
|
||||
this.audibleLocaleCb.Name = "audibleLocaleCb";
|
||||
this.audibleLocaleCb.Size = new System.Drawing.Size(53, 21);
|
||||
this.audibleLocaleCb.TabIndex = 5;
|
||||
//
|
||||
// groupBox1
|
||||
//
|
||||
this.groupBox1.Controls.Add(this.decryptKeyTb);
|
||||
this.groupBox1.Controls.Add(this.decryptKeyLbl);
|
||||
this.groupBox1.Controls.Add(this.decryptKeyDescLbl);
|
||||
this.groupBox1.Controls.Add(this.downloadsInProgressGb);
|
||||
this.groupBox1.Controls.Add(this.decryptInProgressGb);
|
||||
this.groupBox1.Location = new System.Drawing.Point(15, 90);
|
||||
this.groupBox1.Location = new System.Drawing.Point(15, 53);
|
||||
this.groupBox1.Name = "groupBox1";
|
||||
this.groupBox1.Size = new System.Drawing.Size(773, 308);
|
||||
this.groupBox1.Size = new System.Drawing.Size(773, 269);
|
||||
this.groupBox1.TabIndex = 6;
|
||||
this.groupBox1.TabStop = false;
|
||||
this.groupBox1.Text = "Advanced settings for control freaks";
|
||||
@@ -270,10 +213,8 @@
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.cancelBtn;
|
||||
this.ClientSize = new System.Drawing.Size(800, 439);
|
||||
this.ClientSize = new System.Drawing.Size(800, 363);
|
||||
this.Controls.Add(this.groupBox1);
|
||||
this.Controls.Add(this.audibleLocaleCb);
|
||||
this.Controls.Add(this.audibleLocaleLbl);
|
||||
this.Controls.Add(this.cancelBtn);
|
||||
this.Controls.Add(this.saveBtn);
|
||||
this.Controls.Add(this.booksLocationDescLbl);
|
||||
@@ -290,19 +231,15 @@
|
||||
this.decryptInProgressGb.ResumeLayout(false);
|
||||
this.decryptInProgressGb.PerformLayout();
|
||||
this.groupBox1.ResumeLayout(false);
|
||||
this.groupBox1.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
private System.Windows.Forms.Label decryptKeyLbl;
|
||||
private System.Windows.Forms.TextBox decryptKeyTb;
|
||||
private System.Windows.Forms.Label booksLocationLbl;
|
||||
private System.Windows.Forms.TextBox booksLocationTb;
|
||||
private System.Windows.Forms.Button booksLocationSearchBtn;
|
||||
private System.Windows.Forms.Label decryptKeyDescLbl;
|
||||
private System.Windows.Forms.Label booksLocationDescLbl;
|
||||
private System.Windows.Forms.GroupBox downloadsInProgressGb;
|
||||
private System.Windows.Forms.Label downloadsInProgressDescLbl;
|
||||
@@ -314,8 +251,6 @@
|
||||
private System.Windows.Forms.RadioButton decryptInProgressWinTempRb;
|
||||
private System.Windows.Forms.Button saveBtn;
|
||||
private System.Windows.Forms.Button cancelBtn;
|
||||
private System.Windows.Forms.Label audibleLocaleLbl;
|
||||
private System.Windows.Forms.ComboBox audibleLocaleCb;
|
||||
private System.Windows.Forms.GroupBox groupBox1;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using Dinah.Core;
|
||||
using FileManager;
|
||||
using InternalUtilities;
|
||||
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
@@ -18,8 +19,6 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
private void SettingsDialog_Load(object sender, EventArgs e)
|
||||
{
|
||||
this.decryptKeyTb.Text = config.DecryptKey;
|
||||
this.decryptKeyDescLbl.Text = desc(nameof(config.DecryptKey));
|
||||
this.booksLocationDescLbl.Text = desc(nameof(config.Books));
|
||||
this.downloadsInProgressDescLbl.Text = desc(nameof(config.DownloadsInProgressEnum));
|
||||
this.decryptInProgressDescLbl.Text = desc(nameof(config.DecryptInProgressEnum));
|
||||
@@ -37,11 +36,6 @@ namespace LibationWinForms.Dialogs
|
||||
? config.Books
|
||||
: Path.GetDirectoryName(Exe.FileLocationOnDisk);
|
||||
|
||||
this.audibleLocaleCb.Text
|
||||
= !string.IsNullOrWhiteSpace(config.LocaleCountryCode)
|
||||
? config.LocaleCountryCode
|
||||
: "us";
|
||||
|
||||
switch (config.DownloadsInProgressEnum)
|
||||
{
|
||||
case "LibationFiles":
|
||||
@@ -77,8 +71,6 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
private void saveBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
config.DecryptKey = this.decryptKeyTb.Text;
|
||||
config.LocaleCountryCode = this.audibleLocaleCb.Text;
|
||||
config.DownloadsInProgressEnum = downloadsInProgressLibationFilesRb.Checked ? "LibationFiles" : "WinTemp";
|
||||
config.DecryptInProgressEnum = decryptInProgressLibationFilesRb.Checked ? "LibationFiles" : "WinTemp";
|
||||
|
||||
|
||||
60
LibationWinForms/UNTESTED/Form1.Designer.cs
generated
60
LibationWinForms/UNTESTED/Form1.Designer.cs
generated
@@ -36,6 +36,8 @@
|
||||
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
|
||||
this.importToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanLibraryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.liberateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.beginBookBackupsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.beginPdfBackupsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
@@ -44,6 +46,7 @@
|
||||
this.editQuickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.accountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.basicSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.advancedSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
|
||||
@@ -52,6 +55,7 @@
|
||||
this.backupsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.pdfsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.addFilterBtn = new System.Windows.Forms.Button();
|
||||
this.noAccountsYetAddAccountToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.menuStrip1.SuspendLayout();
|
||||
this.statusStrip1.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
@@ -113,7 +117,10 @@
|
||||
// importToolStripMenuItem
|
||||
//
|
||||
this.importToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.scanLibraryToolStripMenuItem});
|
||||
this.noAccountsYetAddAccountToolStripMenuItem,
|
||||
this.scanLibraryToolStripMenuItem,
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem,
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem});
|
||||
this.importToolStripMenuItem.Name = "importToolStripMenuItem";
|
||||
this.importToolStripMenuItem.Size = new System.Drawing.Size(55, 20);
|
||||
this.importToolStripMenuItem.Text = "&Import";
|
||||
@@ -121,10 +128,24 @@
|
||||
// scanLibraryToolStripMenuItem
|
||||
//
|
||||
this.scanLibraryToolStripMenuItem.Name = "scanLibraryToolStripMenuItem";
|
||||
this.scanLibraryToolStripMenuItem.Size = new System.Drawing.Size(138, 22);
|
||||
this.scanLibraryToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.scanLibraryToolStripMenuItem.Text = "Scan &Library";
|
||||
this.scanLibraryToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryToolStripMenuItem_Click);
|
||||
//
|
||||
// scanLibraryOfAllAccountsToolStripMenuItem
|
||||
//
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem.Name = "scanLibraryOfAllAccountsToolStripMenuItem";
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem.Text = "Scan Library of &All Accounts";
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryOfAllAccountsToolStripMenuItem_Click);
|
||||
//
|
||||
// scanLibraryOfSomeAccountsToolStripMenuItem
|
||||
//
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Name = "scanLibraryOfSomeAccountsToolStripMenuItem";
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Text = "Scan Library of &Some Accounts...";
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryOfSomeAccountsToolStripMenuItem_Click);
|
||||
//
|
||||
// liberateToolStripMenuItem
|
||||
//
|
||||
this.liberateToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
@@ -169,7 +190,7 @@
|
||||
//
|
||||
this.editQuickFiltersToolStripMenuItem.Name = "editQuickFiltersToolStripMenuItem";
|
||||
this.editQuickFiltersToolStripMenuItem.Size = new System.Drawing.Size(256, 22);
|
||||
this.editQuickFiltersToolStripMenuItem.Text = "&Edit quick filters";
|
||||
this.editQuickFiltersToolStripMenuItem.Text = "&Edit quick filters...";
|
||||
this.editQuickFiltersToolStripMenuItem.Click += new System.EventHandler(this.EditQuickFiltersToolStripMenuItem_Click);
|
||||
//
|
||||
// toolStripSeparator1
|
||||
@@ -180,24 +201,32 @@
|
||||
// settingsToolStripMenuItem
|
||||
//
|
||||
this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.accountsToolStripMenuItem,
|
||||
this.basicSettingsToolStripMenuItem,
|
||||
this.advancedSettingsToolStripMenuItem});
|
||||
this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem";
|
||||
this.settingsToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
|
||||
this.settingsToolStripMenuItem.Text = "&Settings";
|
||||
//
|
||||
// accountsToolStripMenuItem
|
||||
//
|
||||
this.accountsToolStripMenuItem.Name = "accountsToolStripMenuItem";
|
||||
this.accountsToolStripMenuItem.Size = new System.Drawing.Size(181, 22);
|
||||
this.accountsToolStripMenuItem.Text = "&Accounts...";
|
||||
this.accountsToolStripMenuItem.Click += new System.EventHandler(this.accountsToolStripMenuItem_Click);
|
||||
//
|
||||
// basicSettingsToolStripMenuItem
|
||||
//
|
||||
this.basicSettingsToolStripMenuItem.Name = "basicSettingsToolStripMenuItem";
|
||||
this.basicSettingsToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
|
||||
this.basicSettingsToolStripMenuItem.Text = "&Basic Settings";
|
||||
this.basicSettingsToolStripMenuItem.Size = new System.Drawing.Size(181, 22);
|
||||
this.basicSettingsToolStripMenuItem.Text = "&Basic Settings...";
|
||||
this.basicSettingsToolStripMenuItem.Click += new System.EventHandler(this.basicSettingsToolStripMenuItem_Click);
|
||||
//
|
||||
// advancedSettingsToolStripMenuItem
|
||||
//
|
||||
this.advancedSettingsToolStripMenuItem.Name = "advancedSettingsToolStripMenuItem";
|
||||
this.advancedSettingsToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
|
||||
this.advancedSettingsToolStripMenuItem.Text = "&Advanced Settings";
|
||||
this.advancedSettingsToolStripMenuItem.Size = new System.Drawing.Size(181, 22);
|
||||
this.advancedSettingsToolStripMenuItem.Text = "Ad&vanced Settings...";
|
||||
this.advancedSettingsToolStripMenuItem.Click += new System.EventHandler(this.advancedSettingsToolStripMenuItem_Click);
|
||||
//
|
||||
// statusStrip1
|
||||
@@ -222,7 +251,7 @@
|
||||
// springLbl
|
||||
//
|
||||
this.springLbl.Name = "springLbl";
|
||||
this.springLbl.Size = new System.Drawing.Size(232, 17);
|
||||
this.springLbl.Size = new System.Drawing.Size(233, 17);
|
||||
this.springLbl.Spring = true;
|
||||
//
|
||||
// backupsCountsLbl
|
||||
@@ -234,7 +263,7 @@
|
||||
// pdfsCountsLbl
|
||||
//
|
||||
this.pdfsCountsLbl.Name = "pdfsCountsLbl";
|
||||
this.pdfsCountsLbl.Size = new System.Drawing.Size(219, 17);
|
||||
this.pdfsCountsLbl.Size = new System.Drawing.Size(218, 17);
|
||||
this.pdfsCountsLbl.Text = "| PDFs: NOT d/l\'ed: {0} Downloaded: {1}";
|
||||
//
|
||||
// addFilterBtn
|
||||
@@ -247,6 +276,13 @@
|
||||
this.addFilterBtn.UseVisualStyleBackColor = true;
|
||||
this.addFilterBtn.Click += new System.EventHandler(this.AddFilterBtn_Click);
|
||||
//
|
||||
// noAccountsYetAddAccountToolStripMenuItem
|
||||
//
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Name = "noAccountsYetAddAccountToolStripMenuItem";
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Text = "No accounts yet. A&dd Account...";
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Click += new System.EventHandler(this.noAccountsYetAddAccountToolStripMenuItem_Click);
|
||||
//
|
||||
// Form1
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
@@ -274,6 +310,7 @@
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Panel gridPanel;
|
||||
private System.Windows.Forms.MenuStrip menuStrip1;
|
||||
private System.Windows.Forms.ToolStripMenuItem importToolStripMenuItem;
|
||||
@@ -297,6 +334,9 @@
|
||||
private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
|
||||
private System.Windows.Forms.ToolStripMenuItem basicSettingsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem advancedSettingsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem accountsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem scanLibraryOfAllAccountsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem scanLibraryOfSomeAccountsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem noAccountsYetAddAccountToolStripMenuItem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using Dinah.Core;
|
||||
using Dinah.Core.Drawing;
|
||||
using Dinah.Core.Windows.Forms;
|
||||
using FileManager;
|
||||
using InternalUtilities;
|
||||
using LibationWinForms.Dialogs;
|
||||
|
||||
namespace LibationWinForms
|
||||
@@ -42,6 +43,8 @@ namespace LibationWinForms
|
||||
PictureStorage.SetDefaultImage(PictureSize._300x300, Properties.Resources.default_cover_300x300.ToBytes(format));
|
||||
PictureStorage.SetDefaultImage(PictureSize._500x500, Properties.Resources.default_cover_500x500.ToBytes(format));
|
||||
|
||||
RefreshImportMenu();
|
||||
|
||||
setVisibleCount(null, 0);
|
||||
|
||||
reloadGrid();
|
||||
@@ -55,8 +58,8 @@ namespace LibationWinForms
|
||||
setBackupCounts(null, null);
|
||||
}
|
||||
|
||||
#region reload grid
|
||||
bool isProcessingGridSelect = false;
|
||||
#region reload grid
|
||||
bool isProcessingGridSelect = false;
|
||||
private void reloadGrid()
|
||||
{
|
||||
// suppressed filter while init'ing UI
|
||||
@@ -129,18 +132,21 @@ namespace LibationWinForms
|
||||
|
||||
// update bottom numbers
|
||||
var pending = noProgress + downloadedOnly;
|
||||
var text
|
||||
var statusStripText
|
||||
= !results.Any() ? "No books. Begin by importing your library"
|
||||
: pending > 0 ? string.Format(backupsCountsLbl_Format, noProgress, downloadedOnly, fullyBackedUp)
|
||||
: $"All {"book".PluralizeWithCount(fullyBackedUp)} backed up";
|
||||
statusStrip1.UIThread(() => backupsCountsLbl.Text = text);
|
||||
|
||||
// update menu item
|
||||
var menuItemText
|
||||
= pending > 0
|
||||
? $"{pending} remaining"
|
||||
: "All books have been liberated";
|
||||
Serilog.Log.Logger.Information(menuItemText);
|
||||
|
||||
Serilog.Log.Logger.Information("Book counts. {@DebugInfo}", new { fullyBackedUp, downloadedOnly, noProgress, pending, statusStripText, menuItemText });
|
||||
|
||||
// update UI
|
||||
statusStrip1.UIThread(() => backupsCountsLbl.Text = statusStripText);
|
||||
menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Enabled = pending > 0);
|
||||
menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Text = string.Format(beginBookBackupsToolStripMenuItem_format, menuItemText));
|
||||
}
|
||||
@@ -155,25 +161,28 @@ namespace LibationWinForms
|
||||
var notDownloaded = boolResults.Count(r => !r);
|
||||
|
||||
// update bottom numbers
|
||||
var text
|
||||
var statusStripText
|
||||
= !boolResults.Any() ? ""
|
||||
: notDownloaded > 0 ? string.Format(pdfsCountsLbl_Format, notDownloaded, downloaded)
|
||||
: $"| All {downloaded} PDFs downloaded";
|
||||
statusStrip1.UIThread(() => pdfsCountsLbl.Text = text);
|
||||
|
||||
// update menu item
|
||||
var menuItemText
|
||||
= notDownloaded > 0
|
||||
? $"{notDownloaded} remaining"
|
||||
: "All PDFs have been downloaded";
|
||||
Serilog.Log.Logger.Information(menuItemText);
|
||||
|
||||
Serilog.Log.Logger.Information("PDF counts. {@DebugInfo}", new { downloaded, notDownloaded, statusStripText, menuItemText });
|
||||
|
||||
// update UI
|
||||
statusStrip1.UIThread(() => pdfsCountsLbl.Text = statusStripText);
|
||||
menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Enabled = notDownloaded > 0);
|
||||
menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Text = string.Format(beginPdfBackupsToolStripMenuItem_format, menuItemText));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region filter
|
||||
private void filterHelpBtn_Click(object sender, EventArgs e) => new Dialogs.SearchSyntaxDialog().ShowDialog();
|
||||
private void filterHelpBtn_Click(object sender, EventArgs e) => new SearchSyntaxDialog().ShowDialog();
|
||||
|
||||
private void AddFilterBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
@@ -217,12 +226,57 @@ namespace LibationWinForms
|
||||
doFilter(lastGoodFilter);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region index menu
|
||||
private void scanLibraryToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
#region Import menu
|
||||
public void RefreshImportMenu()
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var count = persister.AccountsSettings.Accounts.Count;
|
||||
|
||||
noAccountsYetAddAccountToolStripMenuItem.Visible = count == 0;
|
||||
scanLibraryToolStripMenuItem.Visible = count == 1;
|
||||
scanLibraryOfAllAccountsToolStripMenuItem.Visible = count > 1;
|
||||
scanLibraryOfSomeAccountsToolStripMenuItem.Visible = count > 1;
|
||||
}
|
||||
|
||||
private void noAccountsYetAddAccountToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
MessageBox.Show("To load your Audible library, come back here to the Import menu after adding your account");
|
||||
new AccountsDialog(this).ShowDialog();
|
||||
}
|
||||
|
||||
private void scanLibraryToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault();
|
||||
scanLibraries(firstAccount);
|
||||
}
|
||||
|
||||
private void scanLibraryOfAllAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var allAccounts = persister.AccountsSettings.GetAll();
|
||||
scanLibraries(allAccounts);
|
||||
}
|
||||
|
||||
private void scanLibraryOfSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var scanAccountsDialog = new ScanAccountsDialog(this);
|
||||
|
||||
if (scanAccountsDialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
if (!scanAccountsDialog.CheckedAccounts.Any())
|
||||
return;
|
||||
|
||||
scanLibraries(scanAccountsDialog.CheckedAccounts);
|
||||
}
|
||||
|
||||
private void scanLibraries(IEnumerable<Account> accounts) => scanLibraries(accounts.ToArray());
|
||||
private void scanLibraries(params Account[] accounts)
|
||||
{
|
||||
using var dialog = new IndexLibraryDialog();
|
||||
using var dialog = new IndexLibraryDialog(accounts);
|
||||
dialog.ShowDialog();
|
||||
|
||||
var totalProcessed = dialog.TotalBooksProcessed;
|
||||
@@ -232,7 +286,7 @@ namespace LibationWinForms
|
||||
|
||||
if (totalProcessed > 0)
|
||||
reloadGrid();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region liberate menu
|
||||
@@ -290,10 +344,12 @@ namespace LibationWinForms
|
||||
}
|
||||
}
|
||||
|
||||
private void EditQuickFiltersToolStripMenuItem_Click(object sender, EventArgs e) => new Dialogs.EditQuickFilters(this).ShowDialog();
|
||||
private void EditQuickFiltersToolStripMenuItem_Click(object sender, EventArgs e) => new EditQuickFilters(this).ShowDialog();
|
||||
#endregion
|
||||
|
||||
#region settings menu
|
||||
private void accountsToolStripMenuItem_Click(object sender, EventArgs e) => new AccountsDialog(this).ShowDialog();
|
||||
|
||||
private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog();
|
||||
|
||||
private void advancedSettingsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
|
||||
@@ -174,17 +174,28 @@ namespace LibationWinForms
|
||||
get
|
||||
{
|
||||
var details = new List<string>();
|
||||
|
||||
var locale
|
||||
= string.IsNullOrWhiteSpace(book.Locale)
|
||||
? "[unknown]"
|
||||
: book.Locale;
|
||||
var acct
|
||||
= string.IsNullOrWhiteSpace(libraryBook.Account)
|
||||
? "[unknown]"
|
||||
: libraryBook.Account;
|
||||
details.Add($"Account: {locale} - {acct}");
|
||||
|
||||
if (book.HasPdf)
|
||||
details.Add("Has PDF");
|
||||
if (book.IsAbridged)
|
||||
details.Add("Abridged");
|
||||
if (book.DatePublished.HasValue)
|
||||
details.Add($"Date pub'd: {book.DatePublished.Value.ToString("MM/dd/yyyy")}");
|
||||
details.Add($"Date pub'd: {book.DatePublished.Value:MM/dd/yyyy}");
|
||||
// this goes last since it's most likely to have a line-break
|
||||
if (!string.IsNullOrWhiteSpace(book.Publisher))
|
||||
details.Add($"Pub: {book.Publisher}");
|
||||
details.Add($"Pub: {book.Publisher.Trim()}");
|
||||
|
||||
if (!details.Any())
|
||||
if (!details.Any())
|
||||
return "[details not imported]";
|
||||
|
||||
return string.Join("\r\n", details);
|
||||
|
||||
21
README.md
21
README.md
@@ -1,5 +1,7 @@
|
||||
# Libation: Liberate your Library
|
||||
|
||||
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
|
||||
|
||||
# Table of Contents
|
||||
|
||||
1. [Audible audiobook manager](#audible-audiobook-manager)
|
||||
@@ -7,6 +9,7 @@
|
||||
- [The bad](#the-bad)
|
||||
- [The ugly](#the-ugly)
|
||||
2. [Getting started](#getting-started)
|
||||
- [Create Accounts](#create-accounts)
|
||||
- [Import your library](#import-your-library)
|
||||
- [Download your books -- DRM-free!](#download-your-books----drm-free)
|
||||
- [Download PDF attachments](#download-pdf-attachments)
|
||||
@@ -30,7 +33,7 @@
|
||||
* Powerful advanced search built on the Lucene search engine
|
||||
* Customizable saved filters for common searches
|
||||
* Open source
|
||||
* Tested on US Audible only. Should theoretically also work for Canada, UK, Germany, and France
|
||||
* Tested on US Audible only. Should theoretically also work for Canada, UK, Germany, France, and Australia
|
||||
|
||||
<a name="theBad"/>
|
||||
|
||||
@@ -53,12 +56,28 @@ I made this for myself and I want to share it with the great programming and aud
|
||||
|
||||
## Getting started
|
||||
|
||||
#### [Download Libation](https://github.com/rmcrackan/Libation/releases)
|
||||
|
||||
### Create Accounts
|
||||
|
||||
Create your account(s):
|
||||
|
||||

|
||||
|
||||
New locale options include many more regions including old audible accounts which pre-date the amazon acquisition
|
||||
|
||||

|
||||
|
||||
### Import your library
|
||||
|
||||
Select Import > Scan Library:
|
||||
|
||||

|
||||
|
||||
Or if you have multiple accounts, you'll get to choose whether to scan all accounts or just the ones you select:
|
||||
|
||||

|
||||
|
||||
You'll see this window while it's scanning:
|
||||
|
||||

|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
-- begin VERSIONING ---------------------------------------------------------------------------------------------------------------------
|
||||
https://github.com/rmcrackan/Libation/releases
|
||||
|
||||
v3.1.8 : Experimental: add Australia to locale options
|
||||
v3.1.7 : Improved logging
|
||||
v3.1.6 : Bugfix: some series indexes/sequences formats cause library not to import
|
||||
v3.1.5 : Bugfix: some series indexes/sequences could cause library not to import
|
||||
v3.1.4 : Bugfix: IsAuthorNarrated was returning no books
|
||||
v3.1.3 : fix weirdness with build number
|
||||
|
||||
142
WinFormsDesigner/Dialogs/AccountsDialog.Designer.cs
generated
Normal file
142
WinFormsDesigner/Dialogs/AccountsDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,142 @@
|
||||
namespace WinFormsDesigner.Dialogs
|
||||
{
|
||||
partial class AccountsDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.cancelBtn = new System.Windows.Forms.Button();
|
||||
this.saveBtn = new System.Windows.Forms.Button();
|
||||
this.dataGridView1 = new System.Windows.Forms.DataGridView();
|
||||
this.DeleteAccount = new System.Windows.Forms.DataGridViewButtonColumn();
|
||||
this.LibraryScan = new System.Windows.Forms.DataGridViewCheckBoxColumn();
|
||||
this.AccountId = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.Locale = new System.Windows.Forms.DataGridViewComboBoxColumn();
|
||||
this.AccountName = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// cancelBtn
|
||||
//
|
||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.cancelBtn.Location = new System.Drawing.Point(713, 415);
|
||||
this.cancelBtn.Name = "cancelBtn";
|
||||
this.cancelBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.cancelBtn.TabIndex = 2;
|
||||
this.cancelBtn.Text = "Cancel";
|
||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// saveBtn
|
||||
//
|
||||
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.saveBtn.Location = new System.Drawing.Point(612, 415);
|
||||
this.saveBtn.Name = "saveBtn";
|
||||
this.saveBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.saveBtn.TabIndex = 1;
|
||||
this.saveBtn.Text = "Save";
|
||||
this.saveBtn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// dataGridView1
|
||||
//
|
||||
this.dataGridView1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.dataGridView1.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells;
|
||||
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||
this.DeleteAccount,
|
||||
this.LibraryScan,
|
||||
this.AccountId,
|
||||
this.Locale,
|
||||
this.AccountName});
|
||||
this.dataGridView1.Location = new System.Drawing.Point(12, 12);
|
||||
this.dataGridView1.MultiSelect = false;
|
||||
this.dataGridView1.Name = "dataGridView1";
|
||||
this.dataGridView1.Size = new System.Drawing.Size(776, 397);
|
||||
this.dataGridView1.TabIndex = 0;
|
||||
//
|
||||
// DeleteAccount
|
||||
//
|
||||
this.DeleteAccount.HeaderText = "Delete";
|
||||
this.DeleteAccount.Name = "DeleteAccount";
|
||||
this.DeleteAccount.ReadOnly = true;
|
||||
this.DeleteAccount.Text = "x";
|
||||
this.DeleteAccount.Width = 44;
|
||||
//
|
||||
// LibraryScan
|
||||
//
|
||||
this.LibraryScan.HeaderText = "Include in library scan?";
|
||||
this.LibraryScan.Name = "LibraryScan";
|
||||
this.LibraryScan.Width = 83;
|
||||
//
|
||||
// AccountId
|
||||
//
|
||||
this.AccountId.HeaderText = "Audible email/login";
|
||||
this.AccountId.Name = "AccountId";
|
||||
this.AccountId.Width = 111;
|
||||
//
|
||||
// Locale
|
||||
//
|
||||
this.Locale.HeaderText = "Locale";
|
||||
this.Locale.Name = "Locale";
|
||||
this.Locale.Width = 45;
|
||||
//
|
||||
// AccountName
|
||||
//
|
||||
this.AccountName.HeaderText = "Account nickname (optional)";
|
||||
this.AccountName.Name = "AccountName";
|
||||
this.AccountName.Width = 152;
|
||||
//
|
||||
// AccountsDialog
|
||||
//
|
||||
this.AcceptButton = this.saveBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.cancelBtn;
|
||||
this.ClientSize = new System.Drawing.Size(800, 450);
|
||||
this.Controls.Add(this.dataGridView1);
|
||||
this.Controls.Add(this.saveBtn);
|
||||
this.Controls.Add(this.cancelBtn);
|
||||
this.Name = "AccountsDialog";
|
||||
this.Text = "Audible Accounts";
|
||||
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Button cancelBtn;
|
||||
private System.Windows.Forms.Button saveBtn;
|
||||
private System.Windows.Forms.DataGridView dataGridView1;
|
||||
private System.Windows.Forms.DataGridViewButtonColumn DeleteAccount;
|
||||
private System.Windows.Forms.DataGridViewCheckBoxColumn LibraryScan;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn AccountId;
|
||||
private System.Windows.Forms.DataGridViewComboBoxColumn Locale;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn AccountName;
|
||||
}
|
||||
}
|
||||
13
WinFormsDesigner/Dialogs/AccountsDialog.cs
Normal file
13
WinFormsDesigner/Dialogs/AccountsDialog.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace WinFormsDesigner.Dialogs
|
||||
{
|
||||
public partial class AccountsDialog : Form
|
||||
{
|
||||
public AccountsDialog(Form1 parent)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
138
WinFormsDesigner/Dialogs/AccountsDialog.resx
Normal file
138
WinFormsDesigner/Dialogs/AccountsDialog.resx
Normal file
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="Original.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="DeleteAccount.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="LibraryScan.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="AccountId.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="AccountName.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="Locale.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
</root>
|
||||
@@ -36,15 +36,15 @@
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Location = new System.Drawing.Point(28, 24);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(260, 13);
|
||||
this.label1.Size = new System.Drawing.Size(263, 13);
|
||||
this.label1.TabIndex = 0;
|
||||
this.label1.Text = "Scanning Audible library. This may take a few minutes";
|
||||
this.label1.Text = "Scanning Audible library. This may take a few minutes.";
|
||||
//
|
||||
// IndexLibraryDialog
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(319, 63);
|
||||
this.ClientSize = new System.Drawing.Size(440, 63);
|
||||
this.Controls.Add(this.label1);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.MaximizeBox = false;
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.passwordLbl = new System.Windows.Forms.Label();
|
||||
this.emailLbl = new System.Windows.Forms.Label();
|
||||
this.passwordTb = new System.Windows.Forms.TextBox();
|
||||
this.emailTb = new System.Windows.Forms.TextBox();
|
||||
this.submitBtn = new System.Windows.Forms.Button();
|
||||
this.localeLbl = new System.Windows.Forms.Label();
|
||||
this.usernameLbl = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// passwordLbl
|
||||
@@ -44,15 +44,6 @@
|
||||
this.passwordLbl.TabIndex = 2;
|
||||
this.passwordLbl.Text = "Password";
|
||||
//
|
||||
// emailLbl
|
||||
//
|
||||
this.emailLbl.AutoSize = true;
|
||||
this.emailLbl.Location = new System.Drawing.Point(12, 15);
|
||||
this.emailLbl.Name = "emailLbl";
|
||||
this.emailLbl.Size = new System.Drawing.Size(32, 13);
|
||||
this.emailLbl.TabIndex = 0;
|
||||
this.emailLbl.Text = "Email";
|
||||
//
|
||||
// passwordTb
|
||||
//
|
||||
this.passwordTb.Location = new System.Drawing.Point(71, 38);
|
||||
@@ -61,13 +52,6 @@
|
||||
this.passwordTb.Size = new System.Drawing.Size(200, 20);
|
||||
this.passwordTb.TabIndex = 3;
|
||||
//
|
||||
// emailTb
|
||||
//
|
||||
this.emailTb.Location = new System.Drawing.Point(71, 12);
|
||||
this.emailTb.Name = "emailTb";
|
||||
this.emailTb.Size = new System.Drawing.Size(200, 20);
|
||||
this.emailTb.TabIndex = 1;
|
||||
//
|
||||
// submitBtn
|
||||
//
|
||||
this.submitBtn.Location = new System.Drawing.Point(196, 64);
|
||||
@@ -77,17 +61,35 @@
|
||||
this.submitBtn.Text = "Submit";
|
||||
this.submitBtn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// localeLbl
|
||||
//
|
||||
this.localeLbl.AutoSize = true;
|
||||
this.localeLbl.Location = new System.Drawing.Point(12, 9);
|
||||
this.localeLbl.Name = "localeLbl";
|
||||
this.localeLbl.Size = new System.Drawing.Size(59, 13);
|
||||
this.localeLbl.TabIndex = 0;
|
||||
this.localeLbl.Text = "Locale: {0}";
|
||||
//
|
||||
// usernameLbl
|
||||
//
|
||||
this.usernameLbl.AutoSize = true;
|
||||
this.usernameLbl.Location = new System.Drawing.Point(12, 22);
|
||||
this.usernameLbl.Name = "usernameLbl";
|
||||
this.usernameLbl.Size = new System.Drawing.Size(75, 13);
|
||||
this.usernameLbl.TabIndex = 1;
|
||||
this.usernameLbl.Text = "Username: {0}";
|
||||
//
|
||||
// AudibleLoginDialog
|
||||
//
|
||||
this.AcceptButton = this.submitBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(283, 99);
|
||||
this.Controls.Add(this.usernameLbl);
|
||||
this.Controls.Add(this.localeLbl);
|
||||
this.Controls.Add(this.submitBtn);
|
||||
this.Controls.Add(this.passwordLbl);
|
||||
this.Controls.Add(this.emailLbl);
|
||||
this.Controls.Add(this.passwordTb);
|
||||
this.Controls.Add(this.emailTb);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
@@ -103,9 +105,9 @@
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label passwordLbl;
|
||||
private System.Windows.Forms.Label emailLbl;
|
||||
private System.Windows.Forms.TextBox passwordTb;
|
||||
private System.Windows.Forms.TextBox emailTb;
|
||||
private System.Windows.Forms.Button submitBtn;
|
||||
private System.Windows.Forms.Label localeLbl;
|
||||
private System.Windows.Forms.Label usernameLbl;
|
||||
}
|
||||
}
|
||||
117
WinFormsDesigner/Dialogs/ScanAccountsDialog.Designer.cs
generated
Normal file
117
WinFormsDesigner/Dialogs/ScanAccountsDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,117 @@
|
||||
namespace WinFormsDesigner.Dialogs
|
||||
{
|
||||
partial class ScanAccountsDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.accountsLbl = new System.Windows.Forms.Label();
|
||||
this.accountsClb = new System.Windows.Forms.CheckedListBox();
|
||||
this.importBtn = new System.Windows.Forms.Button();
|
||||
this.cancelBtn = new System.Windows.Forms.Button();
|
||||
this.editBtn = new System.Windows.Forms.Button();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// accountsLbl
|
||||
//
|
||||
this.accountsLbl.AutoSize = true;
|
||||
this.accountsLbl.Location = new System.Drawing.Point(12, 9);
|
||||
this.accountsLbl.Name = "accountsLbl";
|
||||
this.accountsLbl.Size = new System.Drawing.Size(467, 13);
|
||||
this.accountsLbl.TabIndex = 0;
|
||||
this.accountsLbl.Text = "Check the accounts to scan and import. To change default selections, go to: Setti" +
|
||||
"ngs > Accounts";
|
||||
//
|
||||
// accountsClb
|
||||
//
|
||||
this.accountsClb.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.accountsClb.FormattingEnabled = true;
|
||||
this.accountsClb.Location = new System.Drawing.Point(12, 25);
|
||||
this.accountsClb.Name = "accountsClb";
|
||||
this.accountsClb.Size = new System.Drawing.Size(560, 94);
|
||||
this.accountsClb.TabIndex = 1;
|
||||
//
|
||||
// importBtn
|
||||
//
|
||||
this.importBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.importBtn.Location = new System.Drawing.Point(396, 125);
|
||||
this.importBtn.Name = "importBtn";
|
||||
this.importBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.importBtn.TabIndex = 3;
|
||||
this.importBtn.Text = "Import";
|
||||
this.importBtn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// cancelBtn
|
||||
//
|
||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.cancelBtn.Location = new System.Drawing.Point(497, 125);
|
||||
this.cancelBtn.Name = "cancelBtn";
|
||||
this.cancelBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.cancelBtn.TabIndex = 4;
|
||||
this.cancelBtn.Text = "Cancel";
|
||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// editBtn
|
||||
//
|
||||
this.editBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.editBtn.Location = new System.Drawing.Point(12, 125);
|
||||
this.editBtn.Name = "editBtn";
|
||||
this.editBtn.Size = new System.Drawing.Size(90, 23);
|
||||
this.editBtn.TabIndex = 2;
|
||||
this.editBtn.Text = "Edit accounts";
|
||||
this.editBtn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// ScanAccountsDialog
|
||||
//
|
||||
this.AcceptButton = this.importBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.cancelBtn;
|
||||
this.ClientSize = new System.Drawing.Size(584, 160);
|
||||
this.Controls.Add(this.editBtn);
|
||||
this.Controls.Add(this.cancelBtn);
|
||||
this.Controls.Add(this.importBtn);
|
||||
this.Controls.Add(this.accountsClb);
|
||||
this.Controls.Add(this.accountsLbl);
|
||||
this.Name = "ScanAccountsDialog";
|
||||
this.Text = "Which accounts?";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label accountsLbl;
|
||||
private System.Windows.Forms.CheckedListBox accountsClb;
|
||||
private System.Windows.Forms.Button importBtn;
|
||||
private System.Windows.Forms.Button cancelBtn;
|
||||
private System.Windows.Forms.Button editBtn;
|
||||
}
|
||||
}
|
||||
20
WinFormsDesigner/Dialogs/ScanAccountsDialog.cs
Normal file
20
WinFormsDesigner/Dialogs/ScanAccountsDialog.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace WinFormsDesigner.Dialogs
|
||||
{
|
||||
public partial class ScanAccountsDialog : Form
|
||||
{
|
||||
public ScanAccountsDialog(Form1 parent)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
120
WinFormsDesigner/Dialogs/ScanAccountsDialog.resx
Normal file
120
WinFormsDesigner/Dialogs/ScanAccountsDialog.resx
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
170
WinFormsDesigner/Dialogs/SearchSyntaxDialog.Designer.cs
generated
170
WinFormsDesigner/Dialogs/SearchSyntaxDialog.Designer.cs
generated
@@ -28,95 +28,95 @@
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
this.label3 = new System.Windows.Forms.Label();
|
||||
this.label4 = new System.Windows.Forms.Label();
|
||||
this.label5 = new System.Windows.Forms.Label();
|
||||
this.closeBtn = new System.Windows.Forms.Button();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Location = new System.Drawing.Point(12, 9);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(358, 52);
|
||||
this.label1.TabIndex = 0;
|
||||
this.label1.Text = "Full Lucene query syntax is supported\r\nFields with similar names are synomyns (eg" +
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
this.label3 = new System.Windows.Forms.Label();
|
||||
this.label4 = new System.Windows.Forms.Label();
|
||||
this.label5 = new System.Windows.Forms.Label();
|
||||
this.closeBtn = new System.Windows.Forms.Button();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Location = new System.Drawing.Point(12, 9);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(358, 52);
|
||||
this.label1.TabIndex = 0;
|
||||
this.label1.Text = "Full Lucene query syntax is supported\r\nFields with similar names are synomyns (eg" +
|
||||
": Author, Authors, AuthorNames)\r\n\r\nTAG FORMAT: [tagName]";
|
||||
//
|
||||
// label2
|
||||
//
|
||||
this.label2.AutoSize = true;
|
||||
this.label2.Location = new System.Drawing.Point(12, 71);
|
||||
this.label2.Name = "label2";
|
||||
this.label2.Size = new System.Drawing.Size(118, 65);
|
||||
this.label2.TabIndex = 1;
|
||||
this.label2.Text = "STRING FIELDS\r\n\r\nSearch for wizard of oz:\r\n title:oz\r\n title:\"wizard of o" +
|
||||
//
|
||||
// label2
|
||||
//
|
||||
this.label2.AutoSize = true;
|
||||
this.label2.Location = new System.Drawing.Point(12, 71);
|
||||
this.label2.Name = "label2";
|
||||
this.label2.Size = new System.Drawing.Size(118, 65);
|
||||
this.label2.TabIndex = 1;
|
||||
this.label2.Text = "STRING FIELDS\r\n\r\nSearch for wizard of oz:\r\n title:oz\r\n title:\"wizard of o" +
|
||||
"z\"";
|
||||
//
|
||||
// label3
|
||||
//
|
||||
this.label3.AutoSize = true;
|
||||
this.label3.Location = new System.Drawing.Point(233, 71);
|
||||
this.label3.Name = "label3";
|
||||
this.label3.Size = new System.Drawing.Size(195, 78);
|
||||
this.label3.TabIndex = 2;
|
||||
this.label3.Text = "NUMBER FIELDS\r\n\r\nFind books between 1-100 minutes long\r\n length:[1 TO 100]\r\nF" +
|
||||
//
|
||||
// label3
|
||||
//
|
||||
this.label3.AutoSize = true;
|
||||
this.label3.Location = new System.Drawing.Point(233, 71);
|
||||
this.label3.Name = "label3";
|
||||
this.label3.Size = new System.Drawing.Size(195, 78);
|
||||
this.label3.TabIndex = 2;
|
||||
this.label3.Text = "NUMBER FIELDS\r\n\r\nFind books between 1-100 minutes long\r\n length:[1 TO 100]\r\nF" +
|
||||
"ind books exactly 1 hr long\r\n length:60";
|
||||
//
|
||||
// label4
|
||||
//
|
||||
this.label4.AutoSize = true;
|
||||
this.label4.Location = new System.Drawing.Point(454, 71);
|
||||
this.label4.Name = "label4";
|
||||
this.label4.Size = new System.Drawing.Size(168, 52);
|
||||
this.label4.TabIndex = 3;
|
||||
this.label4.Text = "BOOL FIELDS\r\n\r\nFind books that you haven\'t rated:\r\n -IsRated";
|
||||
//
|
||||
// label5
|
||||
//
|
||||
this.label5.AutoSize = true;
|
||||
this.label5.Location = new System.Drawing.Point(673, 71);
|
||||
this.label5.Name = "label5";
|
||||
this.label5.Size = new System.Drawing.Size(257, 78);
|
||||
this.label5.TabIndex = 4;
|
||||
this.label5.Text = "ID FIELDS\r\n\r\nAlice\'s Adventures in Wonderland (ID: B015D78L0U)\r\n id:B015D78L0" +
|
||||
//
|
||||
// label4
|
||||
//
|
||||
this.label4.AutoSize = true;
|
||||
this.label4.Location = new System.Drawing.Point(454, 71);
|
||||
this.label4.Name = "label4";
|
||||
this.label4.Size = new System.Drawing.Size(168, 52);
|
||||
this.label4.TabIndex = 3;
|
||||
this.label4.Text = "BOOL FIELDS\r\n\r\nFind books that you haven\'t rated:\r\n -IsRated";
|
||||
//
|
||||
// label5
|
||||
//
|
||||
this.label5.AutoSize = true;
|
||||
this.label5.Location = new System.Drawing.Point(673, 71);
|
||||
this.label5.Name = "label5";
|
||||
this.label5.Size = new System.Drawing.Size(257, 78);
|
||||
this.label5.TabIndex = 4;
|
||||
this.label5.Text = "ID FIELDS\r\n\r\nAlice\'s Adventures in Wonderland (ID: B015D78L0U)\r\n id:B015D78L0" +
|
||||
"U\r\n\r\nAll of these are synonyms for the ID field";
|
||||
//
|
||||
// closeBtn
|
||||
//
|
||||
this.closeBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.closeBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.closeBtn.Location = new System.Drawing.Point(890, 415);
|
||||
this.closeBtn.Name = "closeBtn";
|
||||
this.closeBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.closeBtn.TabIndex = 5;
|
||||
this.closeBtn.Text = "Close";
|
||||
this.closeBtn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// SearchSyntaxDialog
|
||||
//
|
||||
this.AcceptButton = this.closeBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.closeBtn;
|
||||
this.ClientSize = new System.Drawing.Size(977, 450);
|
||||
this.Controls.Add(this.closeBtn);
|
||||
this.Controls.Add(this.label5);
|
||||
this.Controls.Add(this.label4);
|
||||
this.Controls.Add(this.label3);
|
||||
this.Controls.Add(this.label2);
|
||||
this.Controls.Add(this.label1);
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "SearchSyntaxDialog";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Filter options";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
//
|
||||
// closeBtn
|
||||
//
|
||||
this.closeBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.closeBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.closeBtn.Location = new System.Drawing.Point(890, 465);
|
||||
this.closeBtn.Name = "closeBtn";
|
||||
this.closeBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.closeBtn.TabIndex = 5;
|
||||
this.closeBtn.Text = "Close";
|
||||
this.closeBtn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// SearchSyntaxDialog
|
||||
//
|
||||
this.AcceptButton = this.closeBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.closeBtn;
|
||||
this.ClientSize = new System.Drawing.Size(977, 500);
|
||||
this.Controls.Add(this.closeBtn);
|
||||
this.Controls.Add(this.label5);
|
||||
this.Controls.Add(this.label4);
|
||||
this.Controls.Add(this.label3);
|
||||
this.Controls.Add(this.label2);
|
||||
this.Controls.Add(this.label1);
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "SearchSyntaxDialog";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Filter options";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
|
||||
79
WinFormsDesigner/Dialogs/SettingsDialog.Designer.cs
generated
79
WinFormsDesigner/Dialogs/SettingsDialog.Designer.cs
generated
@@ -28,12 +28,9 @@
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.decryptKeyLbl = new System.Windows.Forms.Label();
|
||||
this.decryptKeyTb = new System.Windows.Forms.TextBox();
|
||||
this.booksLocationLbl = new System.Windows.Forms.Label();
|
||||
this.booksLocationTb = new System.Windows.Forms.TextBox();
|
||||
this.booksLocationSearchBtn = new System.Windows.Forms.Button();
|
||||
this.decryptKeyDescLbl = new System.Windows.Forms.Label();
|
||||
this.booksLocationDescLbl = new System.Windows.Forms.Label();
|
||||
this.downloadsInProgressGb = new System.Windows.Forms.GroupBox();
|
||||
this.downloadsInProgressLibationFilesRb = new System.Windows.Forms.RadioButton();
|
||||
@@ -45,30 +42,12 @@
|
||||
this.decryptInProgressDescLbl = new System.Windows.Forms.Label();
|
||||
this.saveBtn = new System.Windows.Forms.Button();
|
||||
this.cancelBtn = new System.Windows.Forms.Button();
|
||||
this.audibleLocaleLbl = new System.Windows.Forms.Label();
|
||||
this.audibleLocaleCb = new System.Windows.Forms.ComboBox();
|
||||
this.groupBox1 = new System.Windows.Forms.GroupBox();
|
||||
this.downloadsInProgressGb.SuspendLayout();
|
||||
this.decryptInProgressGb.SuspendLayout();
|
||||
this.groupBox1.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// decryptKeyLbl
|
||||
//
|
||||
this.decryptKeyLbl.AutoSize = true;
|
||||
this.decryptKeyLbl.Location = new System.Drawing.Point(6, 22);
|
||||
this.decryptKeyLbl.Name = "decryptKeyLbl";
|
||||
this.decryptKeyLbl.Size = new System.Drawing.Size(64, 13);
|
||||
this.decryptKeyLbl.TabIndex = 0;
|
||||
this.decryptKeyLbl.Text = "Decrypt key";
|
||||
//
|
||||
// decryptKeyTb
|
||||
//
|
||||
this.decryptKeyTb.Location = new System.Drawing.Point(76, 19);
|
||||
this.decryptKeyTb.Name = "decryptKeyTb";
|
||||
this.decryptKeyTb.Size = new System.Drawing.Size(100, 20);
|
||||
this.decryptKeyTb.TabIndex = 1;
|
||||
//
|
||||
// booksLocationLbl
|
||||
//
|
||||
this.booksLocationLbl.AutoSize = true;
|
||||
@@ -94,15 +73,6 @@
|
||||
this.booksLocationSearchBtn.Text = "...";
|
||||
this.booksLocationSearchBtn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// decryptKeyDescLbl
|
||||
//
|
||||
this.decryptKeyDescLbl.AutoSize = true;
|
||||
this.decryptKeyDescLbl.Location = new System.Drawing.Point(73, 42);
|
||||
this.decryptKeyDescLbl.Name = "decryptKeyDescLbl";
|
||||
this.decryptKeyDescLbl.Size = new System.Drawing.Size(36, 13);
|
||||
this.decryptKeyDescLbl.TabIndex = 2;
|
||||
this.decryptKeyDescLbl.Text = "[desc]";
|
||||
//
|
||||
// booksLocationDescLbl
|
||||
//
|
||||
this.booksLocationDescLbl.AutoSize = true;
|
||||
@@ -117,7 +87,7 @@
|
||||
this.downloadsInProgressGb.Controls.Add(this.downloadsInProgressLibationFilesRb);
|
||||
this.downloadsInProgressGb.Controls.Add(this.downloadsInProgressWinTempRb);
|
||||
this.downloadsInProgressGb.Controls.Add(this.downloadsInProgressDescLbl);
|
||||
this.downloadsInProgressGb.Location = new System.Drawing.Point(15, 58);
|
||||
this.downloadsInProgressGb.Location = new System.Drawing.Point(15, 19);
|
||||
this.downloadsInProgressGb.Name = "downloadsInProgressGb";
|
||||
this.downloadsInProgressGb.Size = new System.Drawing.Size(758, 117);
|
||||
this.downloadsInProgressGb.TabIndex = 4;
|
||||
@@ -162,7 +132,7 @@
|
||||
this.decryptInProgressGb.Controls.Add(this.decryptInProgressLibationFilesRb);
|
||||
this.decryptInProgressGb.Controls.Add(this.decryptInProgressWinTempRb);
|
||||
this.decryptInProgressGb.Controls.Add(this.decryptInProgressDescLbl);
|
||||
this.decryptInProgressGb.Location = new System.Drawing.Point(9, 183);
|
||||
this.decryptInProgressGb.Location = new System.Drawing.Point(9, 144);
|
||||
this.decryptInProgressGb.Name = "decryptInProgressGb";
|
||||
this.decryptInProgressGb.Size = new System.Drawing.Size(758, 117);
|
||||
this.decryptInProgressGb.TabIndex = 5;
|
||||
@@ -205,7 +175,7 @@
|
||||
// saveBtn
|
||||
//
|
||||
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.saveBtn.Location = new System.Drawing.Point(612, 404);
|
||||
this.saveBtn.Location = new System.Drawing.Point(612, 328);
|
||||
this.saveBtn.Name = "saveBtn";
|
||||
this.saveBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.saveBtn.TabIndex = 7;
|
||||
@@ -216,47 +186,20 @@
|
||||
//
|
||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.cancelBtn.Location = new System.Drawing.Point(713, 404);
|
||||
this.cancelBtn.Location = new System.Drawing.Point(713, 328);
|
||||
this.cancelBtn.Name = "cancelBtn";
|
||||
this.cancelBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.cancelBtn.TabIndex = 8;
|
||||
this.cancelBtn.Text = "Cancel";
|
||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// audibleLocaleLbl
|
||||
//
|
||||
this.audibleLocaleLbl.AutoSize = true;
|
||||
this.audibleLocaleLbl.Location = new System.Drawing.Point(12, 56);
|
||||
this.audibleLocaleLbl.Name = "audibleLocaleLbl";
|
||||
this.audibleLocaleLbl.Size = new System.Drawing.Size(77, 13);
|
||||
this.audibleLocaleLbl.TabIndex = 4;
|
||||
this.audibleLocaleLbl.Text = "Audible Locale";
|
||||
//
|
||||
// audibleLocaleCb
|
||||
//
|
||||
this.audibleLocaleCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.audibleLocaleCb.FormattingEnabled = true;
|
||||
this.audibleLocaleCb.Items.AddRange(new object[] {
|
||||
"us",
|
||||
"uk",
|
||||
"germany",
|
||||
"france",
|
||||
"canada"});
|
||||
this.audibleLocaleCb.Location = new System.Drawing.Point(95, 53);
|
||||
this.audibleLocaleCb.Name = "audibleLocaleCb";
|
||||
this.audibleLocaleCb.Size = new System.Drawing.Size(53, 21);
|
||||
this.audibleLocaleCb.TabIndex = 5;
|
||||
//
|
||||
// groupBox1
|
||||
//
|
||||
this.groupBox1.Controls.Add(this.decryptKeyTb);
|
||||
this.groupBox1.Controls.Add(this.decryptKeyLbl);
|
||||
this.groupBox1.Controls.Add(this.decryptKeyDescLbl);
|
||||
this.groupBox1.Controls.Add(this.downloadsInProgressGb);
|
||||
this.groupBox1.Controls.Add(this.decryptInProgressGb);
|
||||
this.groupBox1.Location = new System.Drawing.Point(15, 90);
|
||||
this.groupBox1.Location = new System.Drawing.Point(15, 53);
|
||||
this.groupBox1.Name = "groupBox1";
|
||||
this.groupBox1.Size = new System.Drawing.Size(773, 308);
|
||||
this.groupBox1.Size = new System.Drawing.Size(773, 269);
|
||||
this.groupBox1.TabIndex = 6;
|
||||
this.groupBox1.TabStop = false;
|
||||
this.groupBox1.Text = "Advanced settings for control freaks";
|
||||
@@ -267,10 +210,8 @@
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.cancelBtn;
|
||||
this.ClientSize = new System.Drawing.Size(800, 439);
|
||||
this.ClientSize = new System.Drawing.Size(800, 363);
|
||||
this.Controls.Add(this.groupBox1);
|
||||
this.Controls.Add(this.audibleLocaleCb);
|
||||
this.Controls.Add(this.audibleLocaleLbl);
|
||||
this.Controls.Add(this.cancelBtn);
|
||||
this.Controls.Add(this.saveBtn);
|
||||
this.Controls.Add(this.booksLocationDescLbl);
|
||||
@@ -286,19 +227,15 @@
|
||||
this.decryptInProgressGb.ResumeLayout(false);
|
||||
this.decryptInProgressGb.PerformLayout();
|
||||
this.groupBox1.ResumeLayout(false);
|
||||
this.groupBox1.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
private System.Windows.Forms.Label decryptKeyLbl;
|
||||
private System.Windows.Forms.TextBox decryptKeyTb;
|
||||
private System.Windows.Forms.Label booksLocationLbl;
|
||||
private System.Windows.Forms.TextBox booksLocationTb;
|
||||
private System.Windows.Forms.Button booksLocationSearchBtn;
|
||||
private System.Windows.Forms.Label decryptKeyDescLbl;
|
||||
private System.Windows.Forms.Label booksLocationDescLbl;
|
||||
private System.Windows.Forms.GroupBox downloadsInProgressGb;
|
||||
private System.Windows.Forms.Label downloadsInProgressDescLbl;
|
||||
@@ -310,8 +247,6 @@
|
||||
private System.Windows.Forms.RadioButton decryptInProgressWinTempRb;
|
||||
private System.Windows.Forms.Button saveBtn;
|
||||
private System.Windows.Forms.Button cancelBtn;
|
||||
private System.Windows.Forms.Label audibleLocaleLbl;
|
||||
private System.Windows.Forms.ComboBox audibleLocaleCb;
|
||||
private System.Windows.Forms.GroupBox groupBox1;
|
||||
}
|
||||
}
|
||||
152
WinFormsDesigner/Form1.Designer.cs
generated
152
WinFormsDesigner/Form1.Designer.cs
generated
@@ -1,33 +1,33 @@
|
||||
namespace WinFormsDesigner
|
||||
{
|
||||
partial class Form1
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
partial class Form1
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
|
||||
this.gridPanel = new System.Windows.Forms.Panel();
|
||||
this.filterHelpBtn = new System.Windows.Forms.Button();
|
||||
@@ -36,6 +36,8 @@
|
||||
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
|
||||
this.importToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanLibraryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.liberateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.beginBookBackupsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.beginPdfBackupsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
@@ -44,6 +46,7 @@
|
||||
this.editQuickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.accountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.basicSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.advancedSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
|
||||
@@ -52,6 +55,7 @@
|
||||
this.backupsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.pdfsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.addFilterBtn = new System.Windows.Forms.Button();
|
||||
this.noAccountsYetAddAccountToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.menuStrip1.SuspendLayout();
|
||||
this.statusStrip1.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
@@ -110,7 +114,10 @@
|
||||
// importToolStripMenuItem
|
||||
//
|
||||
this.importToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.scanLibraryToolStripMenuItem});
|
||||
this.noAccountsYetAddAccountToolStripMenuItem,
|
||||
this.scanLibraryToolStripMenuItem,
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem,
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem});
|
||||
this.importToolStripMenuItem.Name = "importToolStripMenuItem";
|
||||
this.importToolStripMenuItem.Size = new System.Drawing.Size(55, 20);
|
||||
this.importToolStripMenuItem.Text = "&Import";
|
||||
@@ -118,9 +125,21 @@
|
||||
// scanLibraryToolStripMenuItem
|
||||
//
|
||||
this.scanLibraryToolStripMenuItem.Name = "scanLibraryToolStripMenuItem";
|
||||
this.scanLibraryToolStripMenuItem.Size = new System.Drawing.Size(138, 22);
|
||||
this.scanLibraryToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.scanLibraryToolStripMenuItem.Text = "Scan &Library";
|
||||
//
|
||||
// scanLibraryOfAllAccountsToolStripMenuItem
|
||||
//
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem.Name = "scanLibraryOfAllAccountsToolStripMenuItem";
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem.Text = "Scan Library of &All Accounts";
|
||||
//
|
||||
// scanLibraryOfSomeAccountsToolStripMenuItem
|
||||
//
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Name = "scanLibraryOfSomeAccountsToolStripMenuItem";
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Text = "Scan Library of &Some Accounts...";
|
||||
//
|
||||
// liberateToolStripMenuItem
|
||||
//
|
||||
this.liberateToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
@@ -162,7 +181,7 @@
|
||||
//
|
||||
this.editQuickFiltersToolStripMenuItem.Name = "editQuickFiltersToolStripMenuItem";
|
||||
this.editQuickFiltersToolStripMenuItem.Size = new System.Drawing.Size(256, 22);
|
||||
this.editQuickFiltersToolStripMenuItem.Text = "&Edit quick filters";
|
||||
this.editQuickFiltersToolStripMenuItem.Text = "&Edit quick filters...";
|
||||
//
|
||||
// toolStripSeparator1
|
||||
//
|
||||
@@ -172,23 +191,30 @@
|
||||
// settingsToolStripMenuItem
|
||||
//
|
||||
this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.accountsToolStripMenuItem,
|
||||
this.basicSettingsToolStripMenuItem,
|
||||
this.advancedSettingsToolStripMenuItem});
|
||||
this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem";
|
||||
this.settingsToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
|
||||
this.settingsToolStripMenuItem.Text = "&Settings";
|
||||
//
|
||||
// accountsToolStripMenuItem
|
||||
//
|
||||
this.accountsToolStripMenuItem.Name = "accountsToolStripMenuItem";
|
||||
this.accountsToolStripMenuItem.Size = new System.Drawing.Size(181, 22);
|
||||
this.accountsToolStripMenuItem.Text = "&Accounts...";
|
||||
//
|
||||
// basicSettingsToolStripMenuItem
|
||||
//
|
||||
this.basicSettingsToolStripMenuItem.Name = "basicSettingsToolStripMenuItem";
|
||||
this.basicSettingsToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
|
||||
this.basicSettingsToolStripMenuItem.Text = "&Basic Settings";
|
||||
this.basicSettingsToolStripMenuItem.Size = new System.Drawing.Size(181, 22);
|
||||
this.basicSettingsToolStripMenuItem.Text = "&Basic Settings...";
|
||||
//
|
||||
// advancedSettingsToolStripMenuItem
|
||||
//
|
||||
this.advancedSettingsToolStripMenuItem.Name = "advancedSettingsToolStripMenuItem";
|
||||
this.advancedSettingsToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
|
||||
this.advancedSettingsToolStripMenuItem.Text = "&Advanced Settings";
|
||||
this.advancedSettingsToolStripMenuItem.Size = new System.Drawing.Size(181, 22);
|
||||
this.advancedSettingsToolStripMenuItem.Text = "Ad&vanced Settings...";
|
||||
//
|
||||
// statusStrip1
|
||||
//
|
||||
@@ -212,7 +238,7 @@
|
||||
// springLbl
|
||||
//
|
||||
this.springLbl.Name = "springLbl";
|
||||
this.springLbl.Size = new System.Drawing.Size(232, 17);
|
||||
this.springLbl.Size = new System.Drawing.Size(233, 17);
|
||||
this.springLbl.Spring = true;
|
||||
//
|
||||
// backupsCountsLbl
|
||||
@@ -224,7 +250,7 @@
|
||||
// pdfsCountsLbl
|
||||
//
|
||||
this.pdfsCountsLbl.Name = "pdfsCountsLbl";
|
||||
this.pdfsCountsLbl.Size = new System.Drawing.Size(219, 17);
|
||||
this.pdfsCountsLbl.Size = new System.Drawing.Size(218, 17);
|
||||
this.pdfsCountsLbl.Text = "| PDFs: NOT d/l\'ed: {0} Downloaded: {1}";
|
||||
//
|
||||
// addFilterBtn
|
||||
@@ -236,6 +262,12 @@
|
||||
this.addFilterBtn.Text = "Add To Quick Filters";
|
||||
this.addFilterBtn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// noAccountsYetAddAccountToolStripMenuItem
|
||||
//
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Name = "noAccountsYetAddAccountToolStripMenuItem";
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Text = "No accounts yet. A&dd Account...";
|
||||
//
|
||||
// Form1
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
@@ -259,32 +291,36 @@
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
private System.Windows.Forms.Panel gridPanel;
|
||||
private System.Windows.Forms.MenuStrip menuStrip1;
|
||||
private System.Windows.Forms.ToolStripMenuItem importToolStripMenuItem;
|
||||
private System.Windows.Forms.StatusStrip statusStrip1;
|
||||
private System.Windows.Forms.ToolStripStatusLabel springLbl;
|
||||
private System.Windows.Forms.ToolStripStatusLabel visibleCountLbl;
|
||||
private System.Windows.Forms.ToolStripMenuItem liberateToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripStatusLabel backupsCountsLbl;
|
||||
private System.Windows.Forms.ToolStripMenuItem beginBookBackupsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripStatusLabel pdfsCountsLbl;
|
||||
private System.Windows.Forms.ToolStripMenuItem beginPdfBackupsToolStripMenuItem;
|
||||
private System.Windows.Forms.TextBox filterSearchTb;
|
||||
private System.Windows.Forms.Button filterBtn;
|
||||
private System.Windows.Forms.Button filterHelpBtn;
|
||||
private System.Windows.Forms.ToolStripMenuItem settingsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem scanLibraryToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem quickFiltersToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem firstFilterIsDefaultToolStripMenuItem;
|
||||
private System.Windows.Forms.Button addFilterBtn;
|
||||
private System.Windows.Forms.ToolStripMenuItem editQuickFiltersToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Panel gridPanel;
|
||||
private System.Windows.Forms.MenuStrip menuStrip1;
|
||||
private System.Windows.Forms.ToolStripMenuItem importToolStripMenuItem;
|
||||
private System.Windows.Forms.StatusStrip statusStrip1;
|
||||
private System.Windows.Forms.ToolStripStatusLabel springLbl;
|
||||
private System.Windows.Forms.ToolStripStatusLabel visibleCountLbl;
|
||||
private System.Windows.Forms.ToolStripMenuItem liberateToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripStatusLabel backupsCountsLbl;
|
||||
private System.Windows.Forms.ToolStripMenuItem beginBookBackupsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripStatusLabel pdfsCountsLbl;
|
||||
private System.Windows.Forms.ToolStripMenuItem beginPdfBackupsToolStripMenuItem;
|
||||
private System.Windows.Forms.TextBox filterSearchTb;
|
||||
private System.Windows.Forms.Button filterBtn;
|
||||
private System.Windows.Forms.Button filterHelpBtn;
|
||||
private System.Windows.Forms.ToolStripMenuItem settingsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem scanLibraryToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem quickFiltersToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem firstFilterIsDefaultToolStripMenuItem;
|
||||
private System.Windows.Forms.Button addFilterBtn;
|
||||
private System.Windows.Forms.ToolStripMenuItem editQuickFiltersToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
|
||||
private System.Windows.Forms.ToolStripMenuItem basicSettingsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem advancedSettingsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem accountsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem scanLibraryOfAllAccountsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem scanLibraryOfSomeAccountsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem noAccountsYetAddAccountToolStripMenuItem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,5 +12,5 @@ namespace WinFormsDesigner
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,12 @@
|
||||
<Compile Include="BookLiberation\DecryptForm.Designer.cs">
|
||||
<DependentUpon>DecryptForm.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Dialogs\AccountsDialog.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Dialogs\AccountsDialog.Designer.cs">
|
||||
<DependentUpon>AccountsDialog.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Dialogs\EditQuickFilters.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -107,6 +113,12 @@
|
||||
<Compile Include="Dialogs\EditTagsDialog.Designer.cs">
|
||||
<DependentUpon>EditTagsDialog.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Dialogs\ScanAccountsDialog.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Dialogs\ScanAccountsDialog.Designer.cs">
|
||||
<DependentUpon>ScanAccountsDialog.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Dialogs\SettingsDialog.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -148,8 +160,12 @@
|
||||
<EmbeddedResource Include="BookLiberation\DecryptForm.resx">
|
||||
<DependentUpon>DecryptForm.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Dialogs\AccountsDialog.resx">
|
||||
<DependentUpon>AccountsDialog.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Dialogs\EditQuickFilters.resx">
|
||||
<DependentUpon>EditQuickFilters.cs</DependentUpon>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Dialogs\EditTagsDialog.resx">
|
||||
<DependentUpon>EditTagsDialog.cs</DependentUpon>
|
||||
@@ -169,6 +185,9 @@
|
||||
<EmbeddedResource Include="Dialogs\Login\_2faCodeDialog.resx">
|
||||
<DependentUpon>_2faCodeDialog.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Dialogs\ScanAccountsDialog.resx">
|
||||
<DependentUpon>ScanAccountsDialog.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Dialogs\SettingsDialog.resx">
|
||||
<DependentUpon>SettingsDialog.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
|
||||
@@ -25,9 +25,9 @@ View > Other Windows > Package Manager Console
|
||||
Default project: DataLayer
|
||||
Startup project: DataLayer
|
||||
since we have mult contexts, must use -context:
|
||||
|
||||
Add-Migration MyComment -context LibationContext
|
||||
Update-Database -context LibationContext
|
||||
Startup project: reset to prev. eg: LibationLauncher
|
||||
|
||||
|
||||
ERROR
|
||||
|
||||
696
_Tests/InternalUtilities.Tests/AccountTests.cs
Normal file
696
_Tests/InternalUtilities.Tests/AccountTests.cs
Normal file
@@ -0,0 +1,696 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AudibleApi;
|
||||
using AudibleApi.Authorization;
|
||||
using Dinah.Core;
|
||||
using FluentAssertions;
|
||||
using FluentAssertions.Common;
|
||||
using InternalUtilities;
|
||||
using Microsoft.VisualStudio.TestPlatform.Common.Filtering;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using TestAudibleApiCommon;
|
||||
using TestCommon;
|
||||
using static AuthorizationShared.Shared;
|
||||
using static AuthorizationShared.Shared.AccessTokenTemporality;
|
||||
using static TestAudibleApiCommon.ComputedTestValues;
|
||||
|
||||
namespace AccountsTests
|
||||
{
|
||||
public class AccountsTestBase
|
||||
{
|
||||
protected const string EMPTY_FILE = "{\r\n \"Accounts\": []\r\n}";
|
||||
|
||||
protected string TestFile;
|
||||
protected Locale usLocale => Localization.Get("us");
|
||||
protected Locale ukLocale => Localization.Get("uk");
|
||||
|
||||
protected void WriteToTestFile(string contents)
|
||||
=> File.WriteAllText(TestFile, contents);
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInit()
|
||||
=> TestFile = Guid.NewGuid() + ".txt";
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
if (File.Exists(TestFile))
|
||||
File.Delete(TestFile);
|
||||
}
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class FromJson : AccountsTestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void _0_accounts()
|
||||
{
|
||||
var accountsSettings = AccountsSettings.FromJson(EMPTY_FILE);
|
||||
accountsSettings.Accounts.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void _1_account_new()
|
||||
{
|
||||
var json = @"
|
||||
{
|
||||
""Accounts"": [
|
||||
{
|
||||
""AccountId"": ""cng"",
|
||||
""AccountName"": ""my main login"",
|
||||
""DecryptKey"": ""asdfasdf"",
|
||||
""IdentityTokens"": null
|
||||
}
|
||||
]
|
||||
}
|
||||
".Trim();
|
||||
var accountsSettings = AccountsSettings.FromJson(json);
|
||||
accountsSettings.Accounts.Count.Should().Be(1);
|
||||
accountsSettings.Accounts[0].AccountId.Should().Be("cng");
|
||||
accountsSettings.Accounts[0].IdentityTokens.Should().BeNull();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void _1_account_populated()
|
||||
{
|
||||
var id = GetIdentityJson(Future);
|
||||
|
||||
var json = $@"
|
||||
{{
|
||||
""Accounts"": [
|
||||
{{
|
||||
""AccountId"": ""cng"",
|
||||
""AccountName"": ""my main login"",
|
||||
""DecryptKey"": ""asdfasdf"",
|
||||
""IdentityTokens"": {id}
|
||||
}}
|
||||
]
|
||||
}}
|
||||
".Trim();
|
||||
var accountsSettings = AccountsSettings.FromJson(json);
|
||||
accountsSettings.Accounts.Count.Should().Be(1);
|
||||
accountsSettings.Accounts[0].AccountId.Should().Be("cng");
|
||||
accountsSettings.Accounts[0].IdentityTokens.Should().NotBeNull();
|
||||
accountsSettings.Accounts[0].IdentityTokens.ExistingAccessToken.TokenValue.Should().Be(AccessTokenValue);
|
||||
}
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class ToJson
|
||||
{
|
||||
[TestMethod]
|
||||
public void serialize()
|
||||
{
|
||||
var id = JsonConvert.SerializeObject(Identity.Empty, Identity.GetJsonSerializerSettings());
|
||||
var jsonIn = $@"
|
||||
{{
|
||||
""Accounts"": [
|
||||
{{
|
||||
""AccountId"": ""cng"",
|
||||
""AccountName"": ""my main login"",
|
||||
""DecryptKey"": ""asdfasdf"",
|
||||
""IdentityTokens"": {id}
|
||||
}}
|
||||
]
|
||||
}}
|
||||
".Trim();
|
||||
var accountsSettings = AccountsSettings.FromJson(jsonIn);
|
||||
|
||||
var jsonOut = accountsSettings.ToJson();
|
||||
jsonOut.Should().Be(@"
|
||||
{
|
||||
""Accounts"": [
|
||||
{
|
||||
""AccountId"": ""cng"",
|
||||
""AccountName"": ""my main login"",
|
||||
""LibraryScan"": true,
|
||||
""DecryptKey"": ""asdfasdf"",
|
||||
""IdentityTokens"": {
|
||||
""LocaleName"": ""[empty]"",
|
||||
""ExistingAccessToken"": {
|
||||
""TokenValue"": ""Atna|"",
|
||||
""Expires"": ""0001-01-01T00:00:00""
|
||||
},
|
||||
""PrivateKey"": null,
|
||||
""AdpToken"": null,
|
||||
""RefreshToken"": null,
|
||||
""Cookies"": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
".Trim());
|
||||
}
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class ctor : AccountsTestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void create_file()
|
||||
{
|
||||
File.Exists(TestFile).Should().BeFalse();
|
||||
var accountsSettings = new AccountsSettings();
|
||||
_ = new AccountsSettingsPersister(accountsSettings, TestFile);
|
||||
File.Exists(TestFile).Should().BeTrue();
|
||||
File.ReadAllText(TestFile).Should().Be(EMPTY_FILE);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void overwrite_existing_file()
|
||||
{
|
||||
File.Exists(TestFile).Should().BeFalse();
|
||||
WriteToTestFile("foo");
|
||||
File.Exists(TestFile).Should().BeTrue();
|
||||
|
||||
var accountsSettings = new AccountsSettings();
|
||||
_ = new AccountsSettingsPersister(accountsSettings, TestFile);
|
||||
File.Exists(TestFile).Should().BeTrue();
|
||||
File.ReadAllText(TestFile).Should().Be(EMPTY_FILE);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void save_multiple_children()
|
||||
{
|
||||
var accountsSettings = new AccountsSettings();
|
||||
accountsSettings.Add(new Account("a0") { AccountName = "n0" });
|
||||
accountsSettings.Add(new Account("a1") { AccountName = "n1" });
|
||||
|
||||
// dispose to cease auto-updates
|
||||
using (var p = new AccountsSettingsPersister(accountsSettings, TestFile)) { }
|
||||
|
||||
var persister = new AccountsSettingsPersister(TestFile);
|
||||
persister.AccountsSettings.Accounts.Count.Should().Be(2);
|
||||
persister.AccountsSettings.Accounts[1].AccountName.Should().Be("n1");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void save_with_identity()
|
||||
{
|
||||
var id = new Identity(usLocale);
|
||||
var idJson = JsonConvert.SerializeObject(id, Identity.GetJsonSerializerSettings());
|
||||
|
||||
var accountsSettings = new AccountsSettings();
|
||||
accountsSettings.Add(new Account("a0") { AccountName = "n0", IdentityTokens = id });
|
||||
|
||||
// dispose to cease auto-updates
|
||||
using (var p = new AccountsSettingsPersister(accountsSettings, TestFile)) { }
|
||||
|
||||
var persister = new AccountsSettingsPersister(TestFile);
|
||||
var acct = persister.AccountsSettings.Accounts[0];
|
||||
acct.AccountName.Should().Be("n0");
|
||||
acct.Locale.CountryCode.Should().Be("us");
|
||||
}
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class save : AccountsTestBase
|
||||
{
|
||||
// add/save account after file creation
|
||||
[TestMethod]
|
||||
public void save_1_account()
|
||||
{
|
||||
// create initial file
|
||||
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile)) { }
|
||||
|
||||
// load file. create account
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
var idIn = new Identity(usLocale);
|
||||
var acctIn = new Account("a0") { AccountName = "n0", IdentityTokens = idIn };
|
||||
|
||||
p.AccountsSettings.Add(acctIn);
|
||||
}
|
||||
|
||||
// re-load file. ensure account still exists
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
p.AccountsSettings.Accounts.Count.Should().Be(1);
|
||||
var acct0 = p.AccountsSettings.Accounts[0];
|
||||
acct0.AccountName.Should().Be("n0");
|
||||
acct0.Locale.CountryCode.Should().Be("us");
|
||||
}
|
||||
}
|
||||
|
||||
// add/save mult accounts after file creation
|
||||
// separately create 2 accounts. ensure both still exist in the end
|
||||
[TestMethod]
|
||||
public void save_2_accounts()
|
||||
{
|
||||
// create initial file
|
||||
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile)) { }
|
||||
|
||||
// load file. create account 0
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
var idIn = new Identity(usLocale);
|
||||
var acctIn = new Account("a0") { AccountName = "n0", IdentityTokens = idIn };
|
||||
|
||||
p.AccountsSettings.Add(acctIn);
|
||||
}
|
||||
|
||||
// re-load file. ensure account still exists
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
p.AccountsSettings.Accounts.Count.Should().Be(1);
|
||||
|
||||
var acct0 = p.AccountsSettings.Accounts[0];
|
||||
acct0.AccountName.Should().Be("n0");
|
||||
acct0.Locale.CountryCode.Should().Be("us");
|
||||
}
|
||||
|
||||
// load file. create account 1
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
var idIn = new Identity(ukLocale);
|
||||
var acctIn = new Account("a1") { AccountName = "n1", IdentityTokens = idIn };
|
||||
|
||||
p.AccountsSettings.Add(acctIn);
|
||||
}
|
||||
|
||||
// re-load file. ensure both accounts still exist
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
p.AccountsSettings.Accounts.Count.Should().Be(2);
|
||||
|
||||
var acct0 = p.AccountsSettings.Accounts[0];
|
||||
acct0.AccountName.Should().Be("n0");
|
||||
acct0.Locale.CountryCode.Should().Be("us");
|
||||
|
||||
var acct1 = p.AccountsSettings.Accounts[1];
|
||||
acct1.AccountName.Should().Be("n1");
|
||||
acct1.Locale.CountryCode.Should().Be("uk");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void update_Account_field_just_added()
|
||||
{
|
||||
// create initial file
|
||||
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile)) { }
|
||||
|
||||
// load file. create 2 accounts
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
var id1 = new Identity(usLocale);
|
||||
var acct1 = new Account("a0") { AccountName = "n0", IdentityTokens = id1 };
|
||||
p.AccountsSettings.Add(acct1);
|
||||
|
||||
// update just-added item. note: this is different than the subscription which happens on initial collection load. ensure this works also
|
||||
acct1.AccountName = "new";
|
||||
}
|
||||
|
||||
// verify save property
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
var acct0 = p.AccountsSettings.Accounts[0];
|
||||
acct0.AccountName.Should().Be("new");
|
||||
}
|
||||
}
|
||||
|
||||
// update Account property. must be non-destructive to all other data
|
||||
[TestMethod]
|
||||
public void update_Account_field()
|
||||
{
|
||||
// create initial file
|
||||
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile)) { }
|
||||
|
||||
// load file. create 2 accounts
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
var id1 = new Identity(usLocale);
|
||||
var acct1 = new Account("a0") { AccountName = "n0", IdentityTokens = id1 };
|
||||
p.AccountsSettings.Add(acct1);
|
||||
|
||||
var id2 = new Identity(ukLocale);
|
||||
var acct2 = new Account("a1") { AccountName = "n1", IdentityTokens = id2 };
|
||||
|
||||
p.AccountsSettings.Add(acct2);
|
||||
}
|
||||
|
||||
// update AccountName on existing file
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
var acct0 = p.AccountsSettings.Accounts[0];
|
||||
acct0.AccountName = "new";
|
||||
}
|
||||
|
||||
// re-load file. ensure both accounts still exist
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
p.AccountsSettings.Accounts.Count.Should().Be(2);
|
||||
|
||||
var acct0 = p.AccountsSettings.Accounts[0];
|
||||
// new
|
||||
acct0.AccountName.Should().Be("new");
|
||||
|
||||
// still here
|
||||
acct0.Locale.CountryCode.Should().Be("us");
|
||||
var acct1 = p.AccountsSettings.Accounts[1];
|
||||
acct1.AccountName.Should().Be("n1");
|
||||
acct1.Locale.CountryCode.Should().Be("uk");
|
||||
}
|
||||
}
|
||||
|
||||
// update identity. must be non-destructive to all other data
|
||||
[TestMethod]
|
||||
public void replace_identity()
|
||||
{
|
||||
// create initial file
|
||||
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile)) { }
|
||||
|
||||
// load file. create 2 accounts
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
var id1 = new Identity(usLocale);
|
||||
var acct1 = new Account("a0") { AccountName = "n0", IdentityTokens = id1 };
|
||||
p.AccountsSettings.Add(acct1);
|
||||
|
||||
var id2 = new Identity(ukLocale);
|
||||
var acct2 = new Account("a1") { AccountName = "n1", IdentityTokens = id2 };
|
||||
|
||||
p.AccountsSettings.Add(acct2);
|
||||
}
|
||||
|
||||
// update identity on existing file
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
var id = new Identity(ukLocale);
|
||||
|
||||
var acct0 = p.AccountsSettings.Accounts[0];
|
||||
acct0.IdentityTokens = id;
|
||||
}
|
||||
|
||||
// re-load file. ensure both accounts still exist
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
p.AccountsSettings.Accounts.Count.Should().Be(2);
|
||||
|
||||
var acct0 = p.AccountsSettings.Accounts[0];
|
||||
// new
|
||||
acct0.Locale.CountryCode.Should().Be("uk");
|
||||
|
||||
// still here
|
||||
acct0.AccountName.Should().Be("n0");
|
||||
var acct1 = p.AccountsSettings.Accounts[1];
|
||||
acct1.AccountName.Should().Be("n1");
|
||||
acct1.Locale.CountryCode.Should().Be("uk");
|
||||
}
|
||||
}
|
||||
|
||||
// multi-level subscribe => update
|
||||
// edit field of existing identity. must be non-destructive to all other data
|
||||
[TestMethod]
|
||||
public void update_identity_field()
|
||||
{
|
||||
// create initial file
|
||||
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile)) { }
|
||||
|
||||
// load file. create 2 accounts
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
var id1 = new Identity(usLocale);
|
||||
var acct1 = new Account("a0") { AccountName = "n0", IdentityTokens = id1 };
|
||||
p.AccountsSettings.Add(acct1);
|
||||
|
||||
var id2 = new Identity(ukLocale);
|
||||
var acct2 = new Account("a1") { AccountName = "n1", IdentityTokens = id2 };
|
||||
|
||||
p.AccountsSettings.Add(acct2);
|
||||
}
|
||||
|
||||
// update identity on existing file
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
p.AccountsSettings.Accounts[0]
|
||||
.IdentityTokens
|
||||
.Update(new AccessToken("Atna|_NEW_", DateTime.Now.AddDays(1)));
|
||||
}
|
||||
|
||||
// re-load file. ensure both accounts still exist
|
||||
using (var p = new AccountsSettingsPersister(TestFile))
|
||||
{
|
||||
p.AccountsSettings.Accounts.Count.Should().Be(2);
|
||||
|
||||
var acct0 = p.AccountsSettings.Accounts[0];
|
||||
// new
|
||||
acct0.IdentityTokens.ExistingAccessToken.TokenValue.Should().Be("Atna|_NEW_");
|
||||
|
||||
// still here
|
||||
acct0.AccountName.Should().Be("n0");
|
||||
acct0.Locale.CountryCode.Should().Be("us");
|
||||
var acct1 = p.AccountsSettings.Accounts[1];
|
||||
acct1.AccountName.Should().Be("n1");
|
||||
acct1.Locale.CountryCode.Should().Be("uk");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class retrieve : AccountsTestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void get_where()
|
||||
{
|
||||
var idUs = new Identity(usLocale);
|
||||
var acct1 = new Account("cng") { IdentityTokens = idUs, AccountName = "foo" };
|
||||
|
||||
var idUk = new Identity(ukLocale);
|
||||
var acct2 = new Account("cng") { IdentityTokens = idUk, AccountName = "bar" };
|
||||
|
||||
var accountsSettings = new AccountsSettings();
|
||||
accountsSettings.Add(acct1);
|
||||
accountsSettings.Add(acct2);
|
||||
|
||||
accountsSettings.GetAccount("cng", "uk").AccountName.Should().Be("bar");
|
||||
}
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class upsert : AccountsTestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void upsert_new()
|
||||
{
|
||||
var accountsSettings = new AccountsSettings();
|
||||
accountsSettings.Accounts.Count.Should().Be(0);
|
||||
|
||||
accountsSettings.Upsert("cng", "us");
|
||||
|
||||
accountsSettings.Accounts.Count.Should().Be(1);
|
||||
accountsSettings.GetAccount("cng", "us").AccountId.Should().Be("cng");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void upsert_exists()
|
||||
{
|
||||
var accountsSettings = new AccountsSettings();
|
||||
var orig = accountsSettings.Upsert("cng", "us");
|
||||
orig.AccountName = "foo";
|
||||
|
||||
var exists = accountsSettings.Upsert("cng", "us");
|
||||
exists.AccountName.Should().Be("foo");
|
||||
|
||||
orig.Should().IsSameOrEqualTo(exists);
|
||||
}
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class delete : AccountsTestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void delete_account()
|
||||
{
|
||||
var accountsSettings = new AccountsSettings();
|
||||
var acct = accountsSettings.Upsert("cng", "us");
|
||||
accountsSettings.Accounts.Count.Should().Be(1);
|
||||
|
||||
var removed = accountsSettings.Delete(acct);
|
||||
removed.Should().BeTrue();
|
||||
|
||||
accountsSettings.Accounts.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void delete_where()
|
||||
{
|
||||
var accountsSettings = new AccountsSettings();
|
||||
_ = accountsSettings.Upsert("cng", "us");
|
||||
accountsSettings.Accounts.Count.Should().Be(1);
|
||||
|
||||
accountsSettings.Delete("baz", "baz").Should().BeFalse();
|
||||
accountsSettings.Accounts.Count.Should().Be(1);
|
||||
|
||||
accountsSettings.Delete("cng", "us").Should().BeTrue();
|
||||
accountsSettings.Accounts.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void delete_updates()
|
||||
{
|
||||
var i = 0;
|
||||
void update(object sender, EventArgs e) => i++;
|
||||
|
||||
var accountsSettings = new AccountsSettings();
|
||||
accountsSettings.Updated += update;
|
||||
|
||||
accountsSettings.Accounts.Count.Should().Be(0);
|
||||
i.Should().Be(0);
|
||||
|
||||
_ = accountsSettings.Upsert("cng", "us");
|
||||
accountsSettings.Accounts.Count.Should().Be(1);
|
||||
i.Should().Be(1);
|
||||
|
||||
accountsSettings.Delete("baz", "baz").Should().BeFalse();
|
||||
accountsSettings.Accounts.Count.Should().Be(1);
|
||||
i.Should().Be(1);
|
||||
|
||||
accountsSettings.Delete("cng", "us").Should().BeTrue();
|
||||
accountsSettings.Accounts.Count.Should().Be(0);
|
||||
i.Should().Be(2); // <== this is the one being tested
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void deleted_account_should_not_persist_file()
|
||||
{
|
||||
Account acct;
|
||||
|
||||
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile))
|
||||
{
|
||||
acct = p.AccountsSettings.Upsert("foo", "us");
|
||||
p.AccountsSettings.Accounts.Count.Should().Be(1);
|
||||
acct.AccountName = "old";
|
||||
}
|
||||
|
||||
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile))
|
||||
{
|
||||
p.AccountsSettings.Delete(acct);
|
||||
p.AccountsSettings.Accounts.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
using (var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile))
|
||||
{
|
||||
File.ReadAllText(TestFile).Should().Be(EMPTY_FILE);
|
||||
|
||||
acct.AccountName = "new";
|
||||
|
||||
File.ReadAllText(TestFile).Should().Be(EMPTY_FILE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// account.Id + Locale.Name -- must be unique
|
||||
[TestClass]
|
||||
public class validate : AccountsTestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void violate_validation()
|
||||
{
|
||||
var accountsSettings = new AccountsSettings();
|
||||
|
||||
var idIn = new Identity(usLocale);
|
||||
|
||||
var a1 = new Account("a") { AccountName = "one", IdentityTokens = idIn };
|
||||
accountsSettings.Add(a1);
|
||||
|
||||
var a2 = new Account("a") { AccountName = "two", IdentityTokens = idIn };
|
||||
|
||||
// violation: validate()
|
||||
Assert.ThrowsException<InvalidOperationException>(() => accountsSettings.Add(a2));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void identity_violate_validation()
|
||||
{
|
||||
var accountsSettings = new AccountsSettings();
|
||||
|
||||
var idIn = new Identity(usLocale);
|
||||
|
||||
var a1 = new Account("a") { AccountName = "one", IdentityTokens = idIn };
|
||||
accountsSettings.Add(a1);
|
||||
|
||||
var a2 = new Account("a") { AccountName = "two" };
|
||||
accountsSettings.Add(a2);
|
||||
|
||||
// violation: GetAccount.SingleOrDefault
|
||||
Assert.ThrowsException<InvalidOperationException>(() => a2.IdentityTokens = idIn);
|
||||
}
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class transactions : AccountsTestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void atomic_update_at_end()
|
||||
{
|
||||
var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile);
|
||||
p.BeginTransation();
|
||||
|
||||
// upserted account will not persist until CommitTransation
|
||||
var acct = p.AccountsSettings.Upsert("cng", "us");
|
||||
acct.AccountName = "foo";
|
||||
|
||||
File.ReadAllText(TestFile).Should().Be(EMPTY_FILE);
|
||||
p.IsInTransaction.Should().BeTrue();
|
||||
|
||||
p.CommitTransation();
|
||||
p.IsInTransaction.Should().BeFalse();
|
||||
|
||||
|
||||
var jsonOut = File.ReadAllText(TestFile);//.Should().Be(EMPTY_FILE);
|
||||
jsonOut.Should().Be(@"
|
||||
{
|
||||
""Accounts"": [
|
||||
{
|
||||
""AccountId"": ""cng"",
|
||||
""AccountName"": ""foo"",
|
||||
""LibraryScan"": true,
|
||||
""DecryptKey"": """",
|
||||
""IdentityTokens"": {
|
||||
""LocaleName"": ""us"",
|
||||
""ExistingAccessToken"": {
|
||||
""TokenValue"": ""Atna|"",
|
||||
""Expires"": ""0001-01-01T00:00:00""
|
||||
},
|
||||
""PrivateKey"": null,
|
||||
""AdpToken"": null,
|
||||
""RefreshToken"": null,
|
||||
""Cookies"": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
".Trim());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void abandoned_transaction()
|
||||
{
|
||||
var p = new AccountsSettingsPersister(new AccountsSettings(), TestFile);
|
||||
try
|
||||
{
|
||||
p.BeginTransation();
|
||||
|
||||
var acct = p.AccountsSettings.Upsert("cng", "us");
|
||||
acct.AccountName = "foo";
|
||||
throw new Exception();
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
File.ReadAllText(TestFile).Should().Be(EMPTY_FILE);
|
||||
p.IsInTransaction.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\audible api\AudibleApi\_Tests\AudibleApi.Tests\AudibleApi.Tests.csproj" />
|
||||
<ProjectReference Include="..\..\..\Dinah.Core\_Tests\TestCommon\TestCommon.csproj" />
|
||||
<ProjectReference Include="..\..\InternalUtilities\InternalUtilities.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
BIN
images/v40_accounts.png
Normal file
BIN
images/v40_accounts.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
BIN
images/v40_import.png
Normal file
BIN
images/v40_import.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
BIN
images/v40_locales.png
Normal file
BIN
images/v40_locales.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Reference in New Issue
Block a user