Bugfix: initial bottom counts can throw error when a book was moved since Libation was last run
This commit is contained in:
parent
0683e5f55b
commit
b1b426427c
@ -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
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user