Bugfix: initial bottom counts can throw error when a book was moved since Libation was last run

This commit is contained in:
Robert McRackan 2019-11-27 16:57:35 -05:00
parent 0683e5f55b
commit b1b426427c
4 changed files with 55 additions and 74 deletions

View File

@ -64,7 +64,8 @@ namespace DataLayer
.Entity<Contributor>() .Entity<Contributor>()
.HasData(Contributor.GetEmpty()); .HasData(Contributor.GetEmpty());
// views are now supported via "query types" (instead of "entity types"): https://docs.microsoft.com/en-us/ef/core/modeling/query-types // views are now supported via "keyless entity types" (instead of "entity types" or the prev "query types"):
} // https://docs.microsoft.com/en-us/ef/core/modeling/keyless-entity-types
} }
}
} }

View File

@ -4,7 +4,6 @@ using System.Linq;
using Dinah.Core.Collections.Generic; using Dinah.Core.Collections.Generic;
using Dinah.EntityFrameworkCore; using Dinah.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace DataLayer namespace DataLayer
{ {
@ -14,24 +13,17 @@ namespace DataLayer
public void Executing(DbContext context) public void Executing(DbContext context)
{ {
// persist tags: var tagsCollection
var modifiedEntities = context = context
.ChangeTracker .ChangeTracker
.Entries() .Entries()
.Where(p => p.State.In(EntityState.Modified, EntityState.Added)) .Where(e => e.State.In(EntityState.Modified, EntityState.Added))
.ToList();
persistTags(modifiedEntities);
}
private static void persistTags(List<EntityEntry> modifiedEntities)
{
var tagsCollection = modifiedEntities
.Select(e => e.Entity as UserDefinedItem) .Select(e => e.Entity as UserDefinedItem)
// filter by null but NOT by blank. blank is the valid way to show the absence of tags .Where(udi => udi != null)
.Where(a => a != null) // do NOT filter out entires with blank tags. blank is the valid way to show the absence of tags
.Select(t => (t.Book.AudibleProductId, t.Tags)) .Select(t => (t.Book.AudibleProductId, t.Tags))
.ToList(); .ToList();
FileManager.TagsPersistence.Save(tagsCollection); FileManager.TagsPersistence.Save(tagsCollection);
} }
} }

View File

@ -2,11 +2,12 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Dinah.Core.Collections.Immutable;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace FileManager namespace FileManager
{ {
public static class FilePathCache public static class FilePathCache
{ {
internal class CacheEntry internal class CacheEntry
{ {
@ -15,22 +16,25 @@ namespace FileManager
public string Path { get; set; } public string Path { get; set; }
} }
static List<CacheEntry> inMemoryCache { get; } = new List<CacheEntry>(); static Cache<CacheEntry> cache { get; } = new Cache<CacheEntry>();
public static string JsonFile => Path.Combine(Configuration.Instance.LibationFiles, "FilePaths.json"); public static string JsonFile => Path.Combine(Configuration.Instance.LibationFiles, "FilePaths.json");
static FilePathCache() static FilePathCache()
{ {
// load json into memory. if file doesn't exist, nothing to do. save() will create if needed // load json into memory. if file doesn't exist, nothing to do. save() will create if needed
if (FileUtility.FileExists(JsonFile)) if (FileUtility.FileExists(JsonFile))
inMemoryCache = JsonConvert.DeserializeObject<List<CacheEntry>>(File.ReadAllText(JsonFile)); {
var list = JsonConvert.DeserializeObject<List<CacheEntry>>(File.ReadAllText(JsonFile));
cache = new Cache<CacheEntry>(list);
}
} }
public static bool Exists(string id, FileType type) => GetPath(id, type) != null; public static bool Exists(string id, FileType type) => GetPath(id, type) != null;
public static string GetPath(string id, FileType type) public static string GetPath(string id, FileType type)
{ {
var entry = inMemoryCache.SingleOrDefault(i => i.Id == id && i.FileType == type); var entry = cache.SingleOrDefault(i => i.Id == id && i.FileType == type);
if (entry == null) if (entry == null)
return null; return null;
@ -44,51 +48,47 @@ namespace FileManager
return entry.Path; return entry.Path;
} }
private static object locker { get; } = new object();
private static void remove(CacheEntry entry) private static void remove(CacheEntry entry)
{ {
lock (locker) cache.Remove(entry);
{ save();
inMemoryCache.Remove(entry); }
save();
}
}
public static void Upsert(string id, FileType type, string path) public static void Upsert(string id, FileType type, string path)
{ {
if (!FileUtility.FileExists(path)) if (!FileUtility.FileExists(path))
throw new FileNotFoundException("Cannot add path to cache. File not found"); throw new FileNotFoundException("Cannot add path to cache. File not found");
lock (locker) var entry = cache.SingleOrDefault(i => i.Id == id && i.FileType == type);
{
var entry = inMemoryCache.SingleOrDefault(i => i.Id == id && i.FileType == type);
if (entry != null)
entry.Path = path;
else
{
entry = new CacheEntry { Id = id, FileType = type, Path = path };
inMemoryCache.Add(entry);
}
save();
}
}
// ONLY call this within lock() if (entry is null)
private static void save() cache.Add(new CacheEntry { Id = id, FileType = type, Path = path });
{ else
// create json if not exists entry.Path = path;
void resave() => File.WriteAllText(JsonFile, JsonConvert.SerializeObject(inMemoryCache, Formatting.Indented));
try { resave(); } save();
catch (IOException) }
{
try { resave(); } // cache is thread-safe and lock free. but file saving is not
catch (IOException ex) private static object locker { get; } = new object();
private static void save()
{
// create json if not exists
static void resave() => File.WriteAllText(JsonFile, JsonConvert.SerializeObject(cache.ToList(), Formatting.Indented));
lock (locker)
{
try { resave(); }
catch (IOException)
{ {
Serilog.Log.Logger.Error(ex, "Error saving FilePaths.json"); try { resave(); }
throw; catch (IOException ex)
} {
} Serilog.Log.Logger.Error(ex, "Error saving FilePaths.json");
throw;
}
}
}
} }
} }
} }

View File

@ -109,21 +109,9 @@ namespace LibationWinForm
.Select(sp => sp.Book) .Select(sp => sp.Book)
.ToList(); .ToList();
// will often fail once if book has been moved while libation is closed. just retry once for now setBookBackupCounts(books);
// fix actual issue later setPdfBackupCounts(books);
// FilePathCache.GetPath() :: inMemoryCache.SingleOrDefault(i => i.Id == id && i.FileType == type) }
try
{
setBookBackupCounts(books);
setPdfBackupCounts(books);
}
catch (Exception ex)
{
System.Threading.Thread.Sleep(100);
setBookBackupCounts(books);
setPdfBackupCounts(books);
}
}
enum AudioFileState { full, aax, none } enum AudioFileState { full, aax, none }
private void setBookBackupCounts(IEnumerable<Book> books) private void setBookBackupCounts(IEnumerable<Book> books)
{ {