Compare commits

...

10 Commits

Author SHA1 Message Date
Robert McRackan
e971d34948 Bug fix: downloading PDFs without also Liberating books -- post-download verification step was failing 2020-09-22 15:43:13 -04:00
Robert McRackan
2b3f67fb99 Merge branch 'master' of https://github.com/rmcrackan/Libation 2020-09-21 13:10:45 -04:00
Robert McRackan
4509b8c8eb Audible whack-a-mole: they changed how to download pdfs 2020-09-21 13:10:36 -04:00
rmcrackan
2e40bebd7d Update README.md 2020-09-11 22:12:25 -04:00
Robert McRackan
dfc4121ab0 add pics for readme 2020-09-11 22:09:44 -04:00
Robert McRackan
3648607d4d export to xlsx 2020-09-11 21:56:56 -04:00
Robert McRackan
b22c35f841 new feature: json export 2020-09-11 21:07:20 -04:00
Robert McRackan
2795690199 New feature: csv export 2020-09-11 17:04:36 -04:00
Robert McRackan
b1f92343cf increm version 2020-09-10 09:07:04 -04:00
Robert McRackan
9e1d657f60 Config setting to retain aax file after decrypt 2020-09-10 09:06:34 -04:00
19 changed files with 462 additions and 32 deletions

View File

@@ -4,6 +4,11 @@
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CsvHelper" Version="15.0.5" />
<PackageReference Include="NPOI" Version="2.5.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\audible api\AudibleApi\AudibleApiDTOs\AudibleApiDTOs.csproj" />
<ProjectReference Include="..\..\audible api\AudibleApi\AudibleApi\AudibleApi.csproj" />

View File

@@ -24,7 +24,7 @@ namespace ApplicationServices
var totalCount = importItems.Count;
Log.Logger.Information($"GetAllLibraryItems: Total count {totalCount}");
var newCount = await getNewCountAsync(importItems);
var newCount = await importIntoDbAsync(importItems);
Log.Logger.Information($"Import: New count {newCount}");
await Task.Run(() => SearchEngineCommands.FullReIndex());
@@ -75,7 +75,7 @@ namespace ApplicationServices
return dtoItems.Select(d => new ImportItem { DtoItem = d, AccountId = account.AccountId, LocaleName = localeName }).ToList();
}
private static async Task<int> getNewCountAsync(List<ImportItem> importItems)
private static async Task<int> importIntoDbAsync(List<ImportItem> importItems)
{
using var context = DbContexts.GetContext();
var libraryImporter = new LibraryImporter(context);

View File

@@ -0,0 +1,268 @@
using System;
using System.Collections.Generic;
using System.Linq;
using CsvHelper;
using CsvHelper.Configuration.Attributes;
using DataLayer;
using NPOI.XSSF.UserModel;
using Serilog;
namespace ApplicationServices
{
public class ExportDto
{
public static string GetName(string fieldName)
{
var property = typeof(ExportDto).GetProperty(fieldName);
var attribute = property.GetCustomAttributes(typeof(NameAttribute), true)[0];
var description = (NameAttribute)attribute;
var text = description.Names;
return text[0];
}
[Name("Account")]
public string Account { get; set; }
[Name("Date Added to library")]
public DateTime DateAdded { get; set; }
[Name("Audible Product Id")]
public string AudibleProductId { get; set; }
[Name("Locale")]
public string Locale { get; set; }
[Name("Title")]
public string Title { get; set; }
[Name("Authors")]
public string AuthorNames { get; set; }
[Name("Narrators")]
public string NarratorNames { get; set; }
[Name("Length In Minutes")]
public int LengthInMinutes { get; set; }
[Name("Publisher")]
public string Publisher { get; set; }
[Name("Pdf url")]
public string PdfUrl { get; set; }
[Name("Series Names")]
public string SeriesNames { get; set; }
[Name("Series Order")]
public string SeriesOrder { get; set; }
[Name("Community Rating: Overall")]
public float? CommunityRatingOverall { get; set; }
[Name("Community Rating: Performance")]
public float? CommunityRatingPerformance { get; set; }
[Name("Community Rating: Story")]
public float? CommunityRatingStory { get; set; }
[Name("Cover Id")]
public string PictureId { get; set; }
[Name("Is Abridged?")]
public bool IsAbridged { get; set; }
[Name("Date Published")]
public DateTime? DatePublished { get; set; }
[Name("Categories")]
public string CategoriesNames { get; set; }
[Name("My Rating: Overall")]
public float? MyRatingOverall { get; set; }
[Name("My Rating: Performance")]
public float? MyRatingPerformance { get; set; }
[Name("My Rating: Story")]
public float? MyRatingStory { get; set; }
[Name("My Libation Tags")]
public string MyLibationTags { get; set; }
}
public static class LibToDtos
{
public static List<ExportDto> ToDtos(this IEnumerable<LibraryBook> library)
=> library.Select(a => new ExportDto
{
Account = a.Account,
DateAdded = a.DateAdded,
AudibleProductId = a.Book.AudibleProductId,
Locale = a.Book.Locale,
Title = a.Book.Title,
AuthorNames = a.Book.AuthorNames,
NarratorNames = a.Book.NarratorNames,
LengthInMinutes = a.Book.LengthInMinutes,
Publisher = a.Book.Publisher,
PdfUrl = a.Book.Supplements?.FirstOrDefault()?.Url,
SeriesNames = a.Book.SeriesNames,
SeriesOrder = a.Book.SeriesLink.Any() ? a.Book.SeriesLink?.Select(sl => $"{sl.Index} : {sl.Series.Name}").Aggregate((a, b) => $"{a}, {b}") : "",
CommunityRatingOverall = a.Book.Rating?.OverallRating,
CommunityRatingPerformance = a.Book.Rating?.PerformanceRating,
CommunityRatingStory = a.Book.Rating?.StoryRating,
PictureId = a.Book.PictureId,
IsAbridged = a.Book.IsAbridged,
DatePublished = a.Book.DatePublished,
CategoriesNames = a.Book.CategoriesNames.Any() ? a.Book.CategoriesNames.Aggregate((a, b) => $"{a}, {b}") : "",
MyRatingOverall = a.Book.UserDefinedItem.Rating.OverallRating,
MyRatingPerformance = a.Book.UserDefinedItem.Rating.PerformanceRating,
MyRatingStory = a.Book.UserDefinedItem.Rating.StoryRating,
MyLibationTags = a.Book.UserDefinedItem.Tags
}).ToList();
}
public static class LibraryExporter
{
public static void ToCsv(string saveFilePath)
{
using var context = DbContexts.GetContext();
var dtos = context.GetLibrary_Flat_NoTracking().ToDtos();
if (!dtos.Any())
return;
using var writer = new System.IO.StreamWriter(saveFilePath);
using var csv = new CsvWriter(writer, System.Globalization.CultureInfo.CurrentCulture);
csv.WriteHeader(typeof(ExportDto));
csv.NextRecord();
csv.WriteRecords(dtos);
}
public static void ToJson(string saveFilePath)
{
using var context = DbContexts.GetContext();
var dtos = context.GetLibrary_Flat_NoTracking().ToDtos();
var json = Newtonsoft.Json.JsonConvert.SerializeObject(dtos, Newtonsoft.Json.Formatting.Indented);
System.IO.File.WriteAllText(saveFilePath, json);
}
public static void ToXlsx(string saveFilePath)
{
using var context = DbContexts.GetContext();
var dtos = context.GetLibrary_Flat_NoTracking().ToDtos();
var workbook = new XSSFWorkbook();
var sheet = workbook.CreateSheet("Library");
var detailSubtotalFont = workbook.CreateFont();
detailSubtotalFont.IsBold = true;
var detailSubtotalCellStyle = workbook.CreateCellStyle();
detailSubtotalCellStyle.SetFont(detailSubtotalFont);
// headers
var rowIndex = 0;
var row = sheet.CreateRow(rowIndex);
var columns = new[] {
nameof (ExportDto.Account),
nameof (ExportDto.DateAdded),
nameof (ExportDto.AudibleProductId),
nameof (ExportDto.Locale),
nameof (ExportDto.Title),
nameof (ExportDto.AuthorNames),
nameof (ExportDto.NarratorNames),
nameof (ExportDto.LengthInMinutes),
nameof (ExportDto.Publisher),
nameof (ExportDto.PdfUrl),
nameof (ExportDto.SeriesNames),
nameof (ExportDto.SeriesOrder),
nameof (ExportDto.CommunityRatingOverall),
nameof (ExportDto.CommunityRatingPerformance),
nameof (ExportDto.CommunityRatingStory),
nameof (ExportDto.PictureId),
nameof (ExportDto.IsAbridged),
nameof (ExportDto.DatePublished),
nameof (ExportDto.CategoriesNames),
nameof (ExportDto.MyRatingOverall),
nameof (ExportDto.MyRatingPerformance),
nameof (ExportDto.MyRatingStory),
nameof (ExportDto.MyLibationTags)
};
var col = 0;
foreach (var c in columns)
{
var cell = row.CreateCell(col++);
var name = ExportDto.GetName(c);
cell.SetCellValue(name);
cell.CellStyle = detailSubtotalCellStyle;
}
var dateFormat = workbook.CreateDataFormat();
var dateStyle = workbook.CreateCellStyle();
dateStyle.DataFormat = dateFormat.GetFormat("MM/dd/yyyy HH:mm:ss");
rowIndex++;
// Add data rows
foreach (var dto in dtos)
{
col = 0;
row = sheet.CreateRow(rowIndex);
row.CreateCell(col++).SetCellValue(dto.Account);
var dateAddedCell = row.CreateCell(col++);
dateAddedCell.CellStyle = dateStyle;
dateAddedCell.SetCellValue(dto.DateAdded);
row.CreateCell(col++).SetCellValue(dto.AudibleProductId);
row.CreateCell(col++).SetCellValue(dto.Locale);
row.CreateCell(col++).SetCellValue(dto.Title);
row.CreateCell(col++).SetCellValue(dto.AuthorNames);
row.CreateCell(col++).SetCellValue(dto.NarratorNames);
row.CreateCell(col++).SetCellValue(dto.LengthInMinutes);
row.CreateCell(col++).SetCellValue(dto.Publisher);
row.CreateCell(col++).SetCellValue(dto.PdfUrl);
row.CreateCell(col++).SetCellValue(dto.SeriesNames);
row.CreateCell(col++).SetCellValue(dto.SeriesOrder);
col = createCell(row, col, dto.CommunityRatingOverall);
col = createCell(row, col, dto.CommunityRatingPerformance);
col = createCell(row, col, dto.CommunityRatingStory);
row.CreateCell(col++).SetCellValue(dto.PictureId);
row.CreateCell(col++).SetCellValue(dto.IsAbridged);
var datePubCell = row.CreateCell(col++);
datePubCell.CellStyle = dateStyle;
if (dto.DatePublished.HasValue)
datePubCell.SetCellValue(dto.DatePublished.Value);
else
datePubCell.SetCellValue("");
row.CreateCell(col++).SetCellValue(dto.CategoriesNames);
col = createCell(row, col, dto.MyRatingOverall);
col = createCell(row, col, dto.MyRatingPerformance);
col = createCell(row, col, dto.MyRatingStory);
row.CreateCell(col++).SetCellValue(dto.MyLibationTags);
rowIndex++;
}
using var fileData = new System.IO.FileStream(saveFilePath, System.IO.FileMode.Create);
workbook.Write(fileData);
}
private static int createCell(NPOI.SS.UserModel.IRow row, int col, float? nullableFloat)
{
if (nullableFloat.HasValue)
row.CreateCell(col++).SetCellValue(nullableFloat.Value);
else
row.CreateCell(col++).SetCellValue("");
return col;
}
}
}

View File

@@ -62,9 +62,21 @@ namespace FileLiberator
if (outputAudioFilename == null)
return new StatusHandler { "Decrypt failed" };
moveFilesToBooksDir(libraryBook.Book, outputAudioFilename);
var destinationDir = moveFilesToBooksDir(libraryBook.Book, outputAudioFilename);
Dinah.Core.IO.FileExt.SafeDelete(aaxFilename);
var config = Configuration.Instance;
if (config.RetainAaxFiles)
{
var newAaxFilename = FileUtility.GetValidFilename(
destinationDir,
Path.GetFileNameWithoutExtension(aaxFilename),
"aax");
File.Move(aaxFilename, newAaxFilename);
}
else
{
Dinah.Core.IO.FileExt.SafeDelete(aaxFilename);
}
var statusHandler = new StatusHandler();
var finalAudioExists = AudibleFileStorage.Audio.Exists(libraryBook.Book.AudibleProductId);
@@ -120,7 +132,7 @@ namespace FileLiberator
}
}
private static void moveFilesToBooksDir(Book product, string outputAudioFilename)
private static string moveFilesToBooksDir(Book product, string outputAudioFilename)
{
// create final directory. move each file into it. MOVE AUDIO FILE LAST
// new dir: safetitle_limit50char + " [" + productId + "]"
@@ -142,7 +154,9 @@ namespace FileLiberator
File.Move(f.FullName, dest);
}
}
return destinationDir;
}
private static string getDestDir(Book product)
{

View File

@@ -45,7 +45,7 @@ namespace FileLiberator
{
validate(libraryBook);
var api = await AudibleApiActions.GetApiAsync(libraryBook.Account, libraryBook.Book.Locale);
var api = await GetApiAsync(libraryBook);
var actualFilePath = await PerformDownloadAsync(
tempAaxFilename,

View File

@@ -31,17 +31,24 @@ namespace FileLiberator
private static string getProposedDownloadFilePath(LibraryBook libraryBook)
{
// if audio file exists, get it's dir. else return base Book dir
var destinationDir =
// this is safe b/c GetDirectoryName(null) == null
Path.GetDirectoryName(AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId))
?? AudibleFileStorage.PDF.StorageDirectory;
var existingPath = Path.GetDirectoryName(AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId));
var file = getdownloadUrl(libraryBook);
return Path.Combine(destinationDir, Path.GetFileName(getdownloadUrl(libraryBook)));
if (existingPath != null)
return Path.Combine(existingPath, Path.GetFileName(file));
var full = FileUtility.GetValidFilename(
AudibleFileStorage.PDF.StorageDirectory,
libraryBook.Book.Title,
Path.GetExtension(file),
libraryBook.Book.AudibleProductId);
return full;
}
private async Task downloadPdfAsync(LibraryBook libraryBook, string proposedDownloadFilePath)
{
var downloadUrl = getdownloadUrl(libraryBook);
var api = await GetApiAsync(libraryBook);
var downloadUrl = await api.GetPdfDownloadLinkAsync(libraryBook.Book.AudibleProductId);
var client = new HttpClient();
var actualDownloadedFilePath = await PerformDownloadAsync(

View File

@@ -38,6 +38,9 @@ namespace FileLiberator
}
}
protected static Task<AudibleApi.Api> GetApiAsync(LibraryBook libraryBook)
=> InternalUtilities.AudibleApiActions.GetApiAsync(libraryBook.Account, libraryBook.Book.Locale);
protected async Task<string> PerformDownloadAsync(string proposedDownloadFilePath, Func<Progress<DownloadProgress>, Task<string>> func)
{
var progress = new Progress<DownloadProgress>();

View File

@@ -83,6 +83,13 @@ namespace FileManager
set => persistentDictionary.Set(nameof(DecryptInProgressEnum), value);
}
[Description("Retain .aax files after decrypting?")]
public bool RetainAaxFiles
{
get => persistentDictionary.Get<bool>(nameof(RetainAaxFiles));
set => persistentDictionary.Set(nameof(RetainAaxFiles), 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

View File

@@ -40,8 +40,13 @@ namespace FileManager
return stringCache[propertyName];
}
public T Get<T>(string propertyName) where T : class
=> GetObject(propertyName) is T obj ? obj : default;
public T Get<T>(string propertyName)
{
var o = GetObject(propertyName);
if (o is null) return default;
if (o is JToken jt) return jt.Value<T>();
return (T)o;
}
public object GetObject(string propertyName)
{

View File

@@ -13,7 +13,7 @@
<!-- <PublishSingleFile>true</PublishSingleFile> -->
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<Version>4.0.2.3</Version>
<Version>4.0.8.1</Version>
</PropertyGroup>
<ItemGroup>

View File

@@ -27,7 +27,8 @@ namespace LibationLauncher
AudibleApiStorage.EnsureAccountsSettingsFileExists();
migrate_v3_to_v4();
migrate_to_v4_0_0();
migrate_to_v4_0_3(); // add setting for whether to delete/retain aax
ensureLoggingConfig();
ensureSerilogConfig();
@@ -83,7 +84,7 @@ namespace LibationLauncher
#region v3 => v4 migration
static string AccountsSettingsFileLegacy30 => Path.Combine(Configuration.Instance.LibationFiles, "IdentityTokens.json");
private static void migrate_v3_to_v4()
private static void migrate_to_v4_0_0()
{
migrateLegacyIdentityFile();
@@ -205,6 +206,27 @@ namespace LibationLauncher
}
#endregion
#region migrate_to_v4_0_3 add setting for whether to delete/retain aax
private static void migrate_to_v4_0_3()
{
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 jRetainAaxFiles = jObj.Property("RetainAaxFiles");
if (jRetainAaxFiles is null)
{
jObj.Add("RetainAaxFiles", false);
var newContents = jObj.ToString(Formatting.Indented);
File.WriteAllText(Configuration.Instance.SettingsFilePath, newContents);
}
}
#endregion
private static string defaultLoggingLevel { get; } = "Information";
private static void ensureLoggingConfig()
{

View File

@@ -27,9 +27,7 @@
<Compile Update="UNTESTED\Dialogs\LibationFilesDialog.Designer.cs">
<DependentUpon>LibationFilesDialog.cs</DependentUpon>
</Compile>
<Compile Update="UNTESTED\Dialogs\ScanAccountsDialog.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="UNTESTED\Dialogs\ScanAccountsDialog.cs" />
<Compile Update="UNTESTED\Dialogs\ScanAccountsDialog.Designer.cs">
<DependentUpon>ScanAccountsDialog.cs</DependentUpon>
</Compile>

View File

@@ -17,15 +17,15 @@ namespace LibationWinForms.BookLiberation
// thread-safe UI updates
public void UpdateFilename(string title) => filenameLbl.UIThread(() => filenameLbl.Text = title);
public void DownloadProgressChanged(long BytesReceived, long TotalBytesToReceive)
public void DownloadProgressChanged(long BytesReceived, long? TotalBytesToReceive)
{
// this won't happen with download file. it will happen with download string
if (TotalBytesToReceive < 0)
if (!TotalBytesToReceive.HasValue || TotalBytesToReceive.Value <= 0)
return;
progressLbl.UIThread(() => progressLbl.Text = $"{BytesReceived:#,##0} of {TotalBytesToReceive:#,##0}");
progressLbl.UIThread(() => progressLbl.Text = $"{BytesReceived:#,##0} of {TotalBytesToReceive.Value:#,##0}");
var d = double.Parse(BytesReceived.ToString()) / double.Parse(TotalBytesToReceive.ToString()) * 100.0;
var d = double.Parse(BytesReceived.ToString()) / double.Parse(TotalBytesToReceive.Value.ToString()) * 100.0;
var i = int.Parse(Math.Truncate(d).ToString());
progressBar1.UIThread(() => progressBar1.Value = i);

View File

@@ -10,6 +10,8 @@ namespace LibationWinForms.BookLiberation
{
public static async Task BackupSingleBookAsync(string productId, EventHandler<LibraryBook> completedAction = null)
{
Serilog.Log.Information("Begin " + nameof(BackupSingleBookAsync) + " {@DebugInfo}", new { productId });
var backupBook = getWiredUpBackupBook(completedAction);
var automatedBackupsForm = attachToBackupsForm(backupBook);
@@ -20,6 +22,8 @@ namespace LibationWinForms.BookLiberation
public static async Task BackupAllBooksAsync(EventHandler<LibraryBook> completedAction = null)
{
Serilog.Log.Information("Begin " + nameof(BackupAllBooksAsync));
var backupBook = getWiredUpBackupBook(completedAction);
var automatedBackupsForm = attachToBackupsForm(backupBook);
@@ -96,6 +100,8 @@ namespace LibationWinForms.BookLiberation
public static async Task BackupAllPdfsAsync(EventHandler<LibraryBook> completedAction = null)
{
Serilog.Log.Information("Begin " + nameof(BackupAllPdfsAsync));
var downloadPdf = getWiredUpDownloadPdf(completedAction);
var automatedBackupsForm = attachToBackupsForm(downloadPdf);
@@ -126,7 +132,7 @@ namespace LibationWinForms.BookLiberation
downloadDialog.UpdateFilename(str);
downloadDialog.Show();
};
downloadFile.DownloadProgressChanged += (_, progress) => downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive.Value);
downloadFile.DownloadProgressChanged += (_, progress) => downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive);
downloadFile.DownloadCompleted += (_, __) => downloadDialog.Close();
await downloadFile.PerformDownloadFileAsync(url, destination);
@@ -161,7 +167,7 @@ namespace LibationWinForms.BookLiberation
void fileDownloadCompleted(object _, string __) => downloadDialog.Close();
void downloadProgressChanged(object _, Dinah.Core.Net.Http.DownloadProgress progress)
=> downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive.Value);
=> downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive);
void unsubscribe(object _ = null, EventArgs __ = null)
{

View File

@@ -35,12 +35,14 @@
this.filterSearchTb = new System.Windows.Forms.TextBox();
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
this.importToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.noAccountsYetAddAccountToolStripMenuItem = 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();
this.exportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.quickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.firstFilterIsDefaultToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.editQuickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
@@ -56,6 +58,7 @@
this.pdfsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel();
this.addFilterBtn = new System.Windows.Forms.Button();
this.noAccountsYetAddAccountToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.exportLibraryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.menuStrip1.SuspendLayout();
this.statusStrip1.SuspendLayout();
this.SuspendLayout();
@@ -106,6 +109,7 @@
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.importToolStripMenuItem,
this.liberateToolStripMenuItem,
this.exportToolStripMenuItem,
this.quickFiltersToolStripMenuItem,
this.settingsToolStripMenuItem});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
@@ -125,6 +129,12 @@
this.importToolStripMenuItem.Size = new System.Drawing.Size(55, 20);
this.importToolStripMenuItem.Text = "&Import";
//
// noAccountsYetAddAccountToolStripMenuItem
//
this.noAccountsYetAddAccountToolStripMenuItem.Name = "noAccountsYetAddAccountToolStripMenuItem";
this.noAccountsYetAddAccountToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
this.noAccountsYetAddAccountToolStripMenuItem.Text = "No accounts yet. A&dd Account...";
//
// scanLibraryToolStripMenuItem
//
this.scanLibraryToolStripMenuItem.Name = "scanLibraryToolStripMenuItem";
@@ -169,6 +179,14 @@
this.beginPdfBackupsToolStripMenuItem.Text = "Begin &PDF Only Backups: {0}";
this.beginPdfBackupsToolStripMenuItem.Click += new System.EventHandler(this.beginPdfBackupsToolStripMenuItem_Click);
//
// exportToolStripMenuItem
//
this.exportToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.exportLibraryToolStripMenuItem});
this.exportToolStripMenuItem.Name = "exportToolStripMenuItem";
this.exportToolStripMenuItem.Size = new System.Drawing.Size(53, 20);
this.exportToolStripMenuItem.Text = "E&xport";
//
// quickFiltersToolStripMenuItem
//
this.quickFiltersToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
@@ -283,6 +301,13 @@
this.noAccountsYetAddAccountToolStripMenuItem.Text = "No accounts yet. A&dd Account...";
this.noAccountsYetAddAccountToolStripMenuItem.Click += new System.EventHandler(this.noAccountsYetAddAccountToolStripMenuItem_Click);
//
// exportLibraryToolStripMenuItem
//
this.exportLibraryToolStripMenuItem.Name = "exportLibraryToolStripMenuItem";
this.exportLibraryToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
this.exportLibraryToolStripMenuItem.Text = "E&xport Library...";
this.exportLibraryToolStripMenuItem.Click += new System.EventHandler(this.exportLibraryToolStripMenuItem_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -338,5 +363,7 @@
private System.Windows.Forms.ToolStripMenuItem scanLibraryOfAllAccountsToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem scanLibraryOfSomeAccountsToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem noAccountsYetAddAccountToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem exportToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem exportLibraryToolStripMenuItem;
}
}

View File

@@ -130,6 +130,9 @@ namespace LibationWinForms
var downloadedOnly = results.Count(r => r == AudioFileState.aax);
var noProgress = results.Count(r => r == AudioFileState.none);
// enable/disable export
exportLibraryToolStripMenuItem.Enabled = results.Any();
// update bottom numbers
var pending = noProgress + downloadedOnly;
var statusStripText
@@ -299,6 +302,45 @@ namespace LibationWinForms
private void updateGridRow(object _, LibraryBook libraryBook) => currProductsGrid.RefreshRow(libraryBook.Book.AudibleProductId);
#endregion
#region Export menu
private void exportLibraryToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
var saveFileDialog = new SaveFileDialog
{
Title = "Where to export Library",
Filter = "Excel Workbook (*.xlsx)|*.xlsx|CSV files (*.csv)|*.csv|JSON files (*.json)|*.json" // + "|All files (*.*)|*.*"
};
if (saveFileDialog.ShowDialog() != DialogResult.OK)
return;
// FilterIndex is 1-based, NOT 0-based
switch (saveFileDialog.FilterIndex)
{
case 1: // xlsx
default:
LibraryExporter.ToXlsx(saveFileDialog.FileName);
break;
case 2: // csv
LibraryExporter.ToCsv(saveFileDialog.FileName);
break;
case 3: // json
LibraryExporter.ToJson(saveFileDialog.FileName);
break;
}
MessageBox.Show("Library exported to:\r\n" + saveFileDialog.FileName);
}
catch (Exception ex)
{
Serilog.Log.Error(ex, "Error attempting to export library");
MessageBox.Show("Error attempting to export your library. Error message:\r\n\r\n" + ex.Message, "Error exporting", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
#endregion
#region quick filters menu
private void loadInitialQuickFilterState()
{

View File

@@ -14,6 +14,7 @@
- [Download your books -- DRM-free!](#download-your-books----drm-free)
- [Download PDF attachments](#download-pdf-attachments)
- [Details of downloaded files](#details-of-downloaded-files)
- [Export your library](#export-your-library)
3. [Searching and filtering](#searching-and-filtering)
- [Tags](#tags)
- [Searches](#searches)
@@ -144,6 +145,12 @@ When you set up Libation, you'll specify a Books directory. Libation looks insid
* .cue: this is a file which logs where chapter breaks occur. Many tools are able to use this if you want to split your book into files along chapter lines.
* .nfo: This is just some general info about the book and includes some technical stats about the audiofile.
### Export your library
![Export](images/Export.png)
Export your library to Excel, CSV, or JSON
## Searching and filtering
### Tags

View File

@@ -35,12 +35,14 @@
this.filterSearchTb = new System.Windows.Forms.TextBox();
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
this.importToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.noAccountsYetAddAccountToolStripMenuItem = 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();
this.exportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.quickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.firstFilterIsDefaultToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.editQuickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
@@ -55,7 +57,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.exportLibraryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.menuStrip1.SuspendLayout();
this.statusStrip1.SuspendLayout();
this.SuspendLayout();
@@ -103,6 +105,7 @@
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.importToolStripMenuItem,
this.liberateToolStripMenuItem,
this.exportToolStripMenuItem,
this.quickFiltersToolStripMenuItem,
this.settingsToolStripMenuItem});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
@@ -122,6 +125,12 @@
this.importToolStripMenuItem.Size = new System.Drawing.Size(55, 20);
this.importToolStripMenuItem.Text = "&Import";
//
// noAccountsYetAddAccountToolStripMenuItem
//
this.noAccountsYetAddAccountToolStripMenuItem.Name = "noAccountsYetAddAccountToolStripMenuItem";
this.noAccountsYetAddAccountToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
this.noAccountsYetAddAccountToolStripMenuItem.Text = "No accounts yet. A&dd Account...";
//
// scanLibraryToolStripMenuItem
//
this.scanLibraryToolStripMenuItem.Name = "scanLibraryToolStripMenuItem";
@@ -161,6 +170,14 @@
this.beginPdfBackupsToolStripMenuItem.Size = new System.Drawing.Size(248, 22);
this.beginPdfBackupsToolStripMenuItem.Text = "Begin &PDF Only Backups: {0}";
//
// exportToolStripMenuItem
//
this.exportToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.exportLibraryToolStripMenuItem});
this.exportToolStripMenuItem.Name = "exportToolStripMenuItem";
this.exportToolStripMenuItem.Size = new System.Drawing.Size(53, 20);
this.exportToolStripMenuItem.Text = "E&xport";
//
// quickFiltersToolStripMenuItem
//
this.quickFiltersToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
@@ -262,11 +279,11 @@
this.addFilterBtn.Text = "Add To Quick Filters";
this.addFilterBtn.UseVisualStyleBackColor = true;
//
// noAccountsYetAddAccountToolStripMenuItem
// exportLibraryToolStripMenuItem
//
this.noAccountsYetAddAccountToolStripMenuItem.Name = "noAccountsYetAddAccountToolStripMenuItem";
this.noAccountsYetAddAccountToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
this.noAccountsYetAddAccountToolStripMenuItem.Text = "No accounts yet. A&dd Account...";
this.exportLibraryToolStripMenuItem.Name = "exportLibraryToolStripMenuItem";
this.exportLibraryToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
this.exportLibraryToolStripMenuItem.Text = "E&xport Library...";
//
// Form1
//
@@ -322,5 +339,7 @@
private System.Windows.Forms.ToolStripMenuItem scanLibraryOfAllAccountsToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem scanLibraryOfSomeAccountsToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem noAccountsYetAddAccountToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem exportToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem exportLibraryToolStripMenuItem;
}
}

BIN
images/Export.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB