Calibre_EntityFrameworkCore

1) Добавление связи между книгами и авторами 2) Добавление

10/15/2021 5:20:42 PM

Changes

src/Calibre.Model.Database/Entities/BooksAuthorsLink.cs 14(+0 -14)

src/Calibre.Model.Database/Entities/Datum.cs 16(+0 -16)

src/Calibre.sln 7(+7 -0)

Details

diff --git a/src/Calibre.Model.Database/BookFileProvider.cs b/src/Calibre.Model.Database/BookFileProvider.cs
new file mode 100644
index 0000000..061c021
--- /dev/null
+++ b/src/Calibre.Model.Database/BookFileProvider.cs
@@ -0,0 +1,67 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+using Calibre.Model.Database.Entities;
+
+namespace Calibre.Model.Database
+{
+    public class BookFileProvider
+    {
+        public async Task<string> BuildFilePathAsync(
+            string bookDirectory,
+            Book book,
+            Func<Book, Task<Data>> fileSelectorActionAsync
+            )
+        {
+            var file = await fileSelectorActionAsync(book);
+
+            var filePath = Path.Combine(
+                bookDirectory,
+                book.Path.Replace('/', '\\'),
+                $"{file.Name}.{file.Format}"
+                );
+
+            return filePath;
+        }
+
+        public async Task ReadFileAsync(
+            string bookDirectory,
+            Book book,
+            Func<Book, Task<Data>> fileSelectorActionAsync,
+            Func<Stream, Task> readActionAsync
+            )
+        {
+            var filePath = await BuildFilePathAsync(
+                bookDirectory,
+                book,
+                fileSelectorActionAsync
+                );
+
+            using (var stream = new FileStream(filePath, FileMode.Open))
+            {
+                await readActionAsync(stream);
+            }
+        }
+
+        public async Task<T> ReadFileAsync<T>(
+            string bookDirectory,
+            Book book,
+            Func<Book, Task<Data>> fileSelectorActionAsync,
+            Func<Stream, Task<T>> readActionAsync
+            ) 
+        {
+            var filePath = await BuildFilePathAsync(
+                bookDirectory, 
+                book, 
+                fileSelectorActionAsync
+                );
+
+            using (var stream = new FileStream(filePath, FileMode.Open))
+            {
+                var result = await readActionAsync(stream);
+                return result;
+            }
+        }        
+    }
+}
diff --git a/src/Calibre.Model.Database/CalibreContext.cs b/src/Calibre.Model.Database/CalibreContext.cs
index 17bdaba..bbfda10 100644
--- a/src/Calibre.Model.Database/CalibreContext.cs
+++ b/src/Calibre.Model.Database/CalibreContext.cs
@@ -4,6 +4,8 @@ using Microsoft.EntityFrameworkCore.Metadata;
 
 #nullable disable
 
+using System.IO;
+
 using Calibre.Model.Database.Entities;
 
 namespace Calibre.Model.Database
@@ -11,6 +13,14 @@ namespace Calibre.Model.Database
     public partial class CalibreContext 
         : DbContext
     {
+        #region
+
+        public string ConnectionString { private set; get; }
+
+        public bool AsNoTracking { set; get; }
+        public DirectoryInfo LibraryDirectory { set; get; }
+
+
         #region Sets
 
         public virtual DbSet<Annotation> Annotations { get; set; }
@@ -37,7 +47,7 @@ namespace Calibre.Model.Database
         public virtual DbSet<Comment> Comments { get; set; }
         public virtual DbSet<ConversionOption> ConversionOptions { get; set; }
         public virtual DbSet<CustomColumn> CustomColumns { get; set; }
-        public virtual DbSet<Datum> Data { get; set; }
+        public virtual DbSet<Data> FileData { get; set; }
         public virtual DbSet<Feed> Feeds { get; set; }
         public virtual DbSet<Identifier> Identifiers { get; set; }
         public virtual DbSet<Language> Languages { get; set; }
@@ -52,11 +62,13 @@ namespace Calibre.Model.Database
 
         #endregion
 
-        private readonly string ConnectionString;
+        #endregion
+        
 
         [Obsolete]
         public CalibreContext()
         {
+            SetupContext();
         }
 
         /// <summary>
@@ -65,15 +77,16 @@ namespace Calibre.Model.Database
         public CalibreContext(string connectionString)
         {
             ConnectionString = connectionString;
+            SetupContext();
         }
 
-        public CalibreContext(DbContextOptions<CalibreContext> options)
-            : base(options)
-        {
-        }
+        //public CalibreContext(DbContextOptions<CalibreContext> options)
+        //    : base(options)
+        //{
+        //}
 
 
-        #region
+        #region BuildModel
 
         protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
         {
@@ -346,22 +359,7 @@ namespace Calibre.Model.Database
 
             modelBuilder.Entity<BooksAuthorsLink>(entity =>
             {
-                entity.ToTable("books_authors_link");
-
-                entity.HasIndex(e => new { e.Book, e.Author }, "IX_books_authors_link_book_author")
-                    .IsUnique();
-
-                entity.HasIndex(e => e.Author, "books_authors_link_aidx");
-
-                entity.HasIndex(e => e.Book, "books_authors_link_bidx");
-
-                entity.Property(e => e.Id)
-                    .ValueGeneratedNever()
-                    .HasColumnName("id");
-
-                entity.Property(e => e.Author).HasColumnName("author");
-
-                entity.Property(e => e.Book).HasColumnName("book");
+                BooksAuthorsLink.EfSetup(entity);
             });
 
             modelBuilder.Entity<BooksLanguagesLink>(entity =>
@@ -470,32 +468,7 @@ namespace Calibre.Model.Database
 
             modelBuilder.Entity<BooksTagsLink>(entity =>
             {
-                entity.ToTable("books_tags_link");
-
-                entity.HasIndex(e => new { e.Book, e.Tag }, "IX_books_tags_link_book_tag")
-                    .IsUnique();
-
-                entity.HasIndex(e => e.Tag, "books_tags_link_aidx");
-
-                entity.HasIndex(e => e.Book, "books_tags_link_bidx");
-
-                entity.Property(e => e.Id)
-                    .ValueGeneratedNever()
-                    .HasColumnName("id");
-
-                entity.Property(e => e.Book).HasColumnName("book");
-
-                entity.Property(e => e.Tag).HasColumnName("tag");
-
-
-                entity
-                    .HasOne(e => e.BookItem)
-                    .WithMany(e => e.Tags)
-                    .HasForeignKey(e => e.Book);
-                entity
-                    .HasOne(e => e.TagItem)
-                    .WithMany(e => e.Books)
-                    .HasForeignKey(e => e.Tag);
+                BooksTagsLink.EfSetup(entity);
             });
 
             modelBuilder.Entity<Comment>(entity =>
@@ -596,32 +569,9 @@ namespace Calibre.Model.Database
                     .HasColumnName("normalized");
             });
 
-            modelBuilder.Entity<Datum>(entity =>
+            modelBuilder.Entity<Data>(entity =>
             {
-                entity.ToTable("data");
-
-                entity.HasIndex(e => new { e.Book, e.Format }, "IX_data_book_format")
-                    .IsUnique();
-
-                entity.HasIndex(e => e.Book, "data_idx");
-
-                entity.HasIndex(e => e.Format, "formats_idx");
-
-                entity.Property(e => e.Id)
-                    .ValueGeneratedNever()
-                    .HasColumnName("id");
-
-                entity.Property(e => e.Book).HasColumnName("book");
-
-                entity.Property(e => e.Format)
-                    .IsRequired()
-                    .HasColumnName("format");
-
-                entity.Property(e => e.Name)
-                    .IsRequired()
-                    .HasColumnName("name");
-
-                entity.Property(e => e.UncompressedSize).HasColumnName("uncompressed_size");
+                Data.EfSetup(entity);
             });
 
             modelBuilder.Entity<Feed>(entity =>
@@ -848,6 +798,18 @@ namespace Calibre.Model.Database
 
         partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
 
+        private void SetupContext() 
+        {
+            if (AsNoTracking)
+            {
+                ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll;
+            }
+            else 
+            {
+                ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
+            }            
+        }
+
         #endregion
     }
 }
diff --git a/src/Calibre.Model.Database/Entities/Authors/BooksAuthorsLink.cs b/src/Calibre.Model.Database/Entities/Authors/BooksAuthorsLink.cs
new file mode 100644
index 0000000..e6d3264
--- /dev/null
+++ b/src/Calibre.Model.Database/Entities/Authors/BooksAuthorsLink.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+
+#nullable disable
+
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace Calibre.Model.Database.Entities
+{
+    public partial class BooksAuthorsLink
+    {
+        public long Id { get; set; }              
+
+        public long Book { get; set; }
+        public virtual Book BookItem { get; set; }
+
+        public long Author { get; set; }
+        public virtual Author AuthorItem { get; set; }
+
+
+        public override string ToString()
+        {
+            return $"{Author}|{AuthorItem?.Name}|{Book}|{BookItem?.Title}";
+        }
+
+
+        internal static void EfSetup(
+            EntityTypeBuilder<BooksAuthorsLink> entity
+            )
+        {
+            entity.ToTable("books_authors_link");
+
+            entity.HasIndex(e => new { e.Book, e.Author }, "IX_books_authors_link_book_author")
+                .IsUnique();
+
+            entity.HasIndex(e => e.Author, "books_authors_link_aidx");
+
+            entity.HasIndex(e => e.Book, "books_authors_link_bidx");
+
+            entity.Property(e => e.Id)
+                .ValueGeneratedNever()
+                .HasColumnName("id");
+
+            entity.Property(e => e.Author).HasColumnName("author");
+
+            entity.Property(e => e.Book).HasColumnName("book");
+
+
+            entity
+                .HasOne(e => e.BookItem)
+                .WithMany(e => e.Autors)
+                .HasForeignKey(e => e.Book);
+            entity
+                .HasOne(e => e.AuthorItem)
+                .WithMany(e => e.Books)
+                .HasForeignKey(e => e.Author);
+        }
+    }
+}
diff --git a/src/Calibre.Model.Database/Entities/Book.cs b/src/Calibre.Model.Database/Entities/Book.cs
index 3d54a48..4012422 100644
--- a/src/Calibre.Model.Database/Entities/Book.cs
+++ b/src/Calibre.Model.Database/Entities/Book.cs
@@ -8,6 +8,9 @@ namespace Calibre.Model.Database.Entities
     public partial class Book
     {
         public long Id { get; set; }
+        /// <summary>
+        /// Заголовок
+        /// </summary>
         public string Title { get; set; }
         public string Sort { get; set; }
         public byte[] Timestamp { get; set; }
@@ -16,13 +19,22 @@ namespace Calibre.Model.Database.Entities
         public string AuthorSort { get; set; }
         public string Isbn { get; set; }
         public string Lccn { get; set; }
+        /// <summary>
+        /// Относительный путь к папке с файлами книги
+        /// </summary>
         public string Path { get; set; }
         public long Flags { get; set; }
         public string Uuid { get; set; }
         public byte[] HasCover { get; set; }
         public byte[] LastModified { get; set; }
+
         public virtual List<BooksTagsLink> Tags { get; set; } 
             = new List<BooksTagsLink>();
+        public virtual List<BooksAuthorsLink> Autors { get; set; }
+            = new List<BooksAuthorsLink>();
+        public virtual List<Data> FileData { get; set; }
+            = new List<Data>();
+
 
         public override string ToString()
         {
diff --git a/src/Calibre.Model.Database/Entities/Data.cs b/src/Calibre.Model.Database/Entities/Data.cs
new file mode 100644
index 0000000..f7f75c8
--- /dev/null
+++ b/src/Calibre.Model.Database/Entities/Data.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+
+#nullable disable
+
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace Calibre.Model.Database.Entities
+{
+    /// <summary>
+    /// Файлы книги
+    /// </summary>
+    public partial class Data
+    {
+        public long Id { get; set; }
+
+        public long Book { get; set; }
+        public Book BookItem { get; set; }
+
+        public string Format { get; set; }
+        public long UncompressedSize { get; set; }
+        public string Name { get; set; }
+
+
+        internal static void EfSetup(
+            EntityTypeBuilder<Data> entity
+            )
+        {
+            entity.ToTable("data");
+
+            entity.HasIndex(e => new { e.Book, e.Format }, "IX_data_book_format")
+                .IsUnique();
+
+            entity.HasIndex(e => e.Book, "data_idx");
+
+            entity.HasIndex(e => e.Format, "formats_idx");
+
+            entity.Property(e => e.Id)
+                .ValueGeneratedNever()
+                .HasColumnName("id");
+
+            entity.Property(e => e.Book).HasColumnName("book");
+
+            entity.Property(e => e.Format)
+                .IsRequired()
+                .HasColumnName("format");
+
+            entity.Property(e => e.Name)
+                .IsRequired()
+                .HasColumnName("name");
+
+            entity.Property(e => e.UncompressedSize).HasColumnName("uncompressed_size");
+
+
+            entity.HasOne(e => e.BookItem)
+                .WithMany(e => e.FileData)
+                .HasForeignKey(e => e.Book);
+        }
+    }
+}

src/Calibre.sln 7(+7 -0)

diff --git a/src/Calibre.sln b/src/Calibre.sln
index d65cffe..cb6c14a 100644
--- a/src/Calibre.sln
+++ b/src/Calibre.sln
@@ -11,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Model", "Model", "{951387AC
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{5654144E-E0B3-4C6D-AFEB-799E77215B4E}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tools.PdfProvider", "Tools.PdfProvider\Tools.PdfProvider.csproj", "{057A6C6C-DC6B-4BA2-9D25-C263C6F04B68}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -25,6 +27,10 @@ Global
 		{4AA0EAB2-7B70-495A-9769-1665B857A91B}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{4AA0EAB2-7B70-495A-9769-1665B857A91B}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{4AA0EAB2-7B70-495A-9769-1665B857A91B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{057A6C6C-DC6B-4BA2-9D25-C263C6F04B68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{057A6C6C-DC6B-4BA2-9D25-C263C6F04B68}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{057A6C6C-DC6B-4BA2-9D25-C263C6F04B68}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{057A6C6C-DC6B-4BA2-9D25-C263C6F04B68}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -32,6 +38,7 @@ Global
 	GlobalSection(NestedProjects) = preSolution
 		{39E1440C-9955-4744-8A22-02ED52502C11} = {5654144E-E0B3-4C6D-AFEB-799E77215B4E}
 		{4AA0EAB2-7B70-495A-9769-1665B857A91B} = {951387AC-1771-4B72-B885-B5DFAFB55D4D}
+		{057A6C6C-DC6B-4BA2-9D25-C263C6F04B68} = {951387AC-1771-4B72-B885-B5DFAFB55D4D}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {362F0FEB-FB13-46F0-825D-15E6F10100D4}
diff --git a/src/Calibre/Calibre.csproj b/src/Calibre/Calibre.csproj
index d42fee7..a69768f 100644
--- a/src/Calibre/Calibre.csproj
+++ b/src/Calibre/Calibre.csproj
@@ -15,6 +15,7 @@
 
   <ItemGroup>
     <ProjectReference Include="..\Calibre.Model.Database\Calibre.Model.Database.csproj" />
+    <ProjectReference Include="..\Tools.PdfProvider\Tools.PdfProvider.csproj" />
   </ItemGroup>
 
 </Project>
diff --git a/src/Calibre/Program.cs b/src/Calibre/Program.cs
index 0ba59fc..f90f828 100644
--- a/src/Calibre/Program.cs
+++ b/src/Calibre/Program.cs
@@ -1,13 +1,15 @@
 using System;
-
 using System.Collections.Generic;
 using System.Threading.Tasks;
 using System.Linq;
 using System.IO;
 using System.Diagnostics;
+using System.IO;
 
 using Microsoft.EntityFrameworkCore;
 
+using Tools.PdfProvider;
+
 using Calibre.Model.Database;
 using Calibre.Model.Database.Entities;
 
@@ -31,20 +33,29 @@ namespace Calibre
         }
 
         private static async Task<Dictionary<string, Book[]>> SearchBooksByTitle(
-            string[] connectionStrings,
+            string[] dbFiles,
             string containsTitle
             )
         {
             List<CalibreContext> contexts 
-                = new List<CalibreContext>(connectionStrings.Length);
+                = new List<CalibreContext>(containsTitle.Length);
             Dictionary<string, Task<Book[]>> data 
-                = new Dictionary<string, Task<Book[]>>(connectionStrings.Length);
+                = new Dictionary<string, Task<Book[]>>(containsTitle.Length);
 
             try
             {
-                foreach (var elem in connectionStrings)
+                foreach (var elem in dbFiles)
                 {
-                    var context = new CalibreContext(elem);
+                    var connectionString = BuildConnectionStringFromPath(elem);
+                    var libraryDirectory = new DirectoryInfo(
+                        Path.GetDirectoryName(elem)
+                        );
+
+                    var context = new CalibreContext(connectionString) 
+                    {
+                        AsNoTracking = false,
+                        LibraryDirectory = libraryDirectory
+                    };
                     contexts.Add(context);
 
                     IQueryable<Book> selectExpr = context
@@ -52,6 +63,13 @@ namespace Calibre
                         .Include(e => e.Tags)
                         .ThenInclude(e => e.TagItem);
 
+                    selectExpr = selectExpr
+                        .Include(e => e.FileData);
+
+                    selectExpr = selectExpr
+                        .Include(e => e.Autors)
+                        .ThenInclude(e => e.AuthorItem);
+
                     if (string.IsNullOrEmpty(containsTitle))
                     {
                         selectExpr = selectExpr
@@ -112,13 +130,7 @@ namespace Calibre
             var watch = Stopwatch.StartNew();
 
             var dbFiles = SelectDbFiles(directory);
-            var connectionStrings = dbFiles
-                .Select(
-                    e => BuildConnectionStringFromPath(e)
-                )
-                .ToArray();
-
-            var searchResult = await SearchBooksByTitle(connectionStrings, "")
+            var searchResult = await SearchBooksByTitle(dbFiles, "")
                 .ConfigureAwait(false);
             watch.Stop();
 
@@ -136,7 +148,9 @@ namespace Calibre
             var searchIgnoreCase = allBooks
                 .Where(
                     e => 
+                        //Заголовок
                         e.Title.Contains(containsTitle, StringComparison.OrdinalIgnoreCase)
+                        //Теги
                         || e.Tags.Any(
                                 e => e.TagItem.Name.Contains(containsTitle, StringComparison.OrdinalIgnoreCase)
                                 )
@@ -147,6 +161,39 @@ namespace Calibre
             {
                 Console.WriteLine($"{elem.Title}|{elem.AuthorSort}");
             }
+
+            var book = searchIgnoreCase.First();
+
+            var dbFile = searchResult
+                .First(
+                    e => e.Value.Any(e2 => e2.Title == book.Title)
+                )
+                .Key;
+
+            BookFileProvider bookFileProvider = new BookFileProvider();
+            //var path = await bookFileProvider.BuildFilePathAsync(
+            //    Path.GetDirectoryName(dbFile),
+            //    book,
+            //    (b) => Task.FromResult(b.FileData.First())
+            //    );
+
+            var bookText = await bookFileProvider.ReadFileAsync(
+                    Path.GetDirectoryName(dbFile),
+                    book,
+                    (b) => Task.FromResult(b.FileData.First()),
+                    async (s) =>
+                    {
+                        SimplePdfReader simplePdfReader = new SimplePdfReader();
+                        var result = await simplePdfReader.ReadPdfAsync(s);
+                        return result;
+                    }
+                );
+
+            var booksWithPdf = allBooks
+                .Where(
+                    e => e.FileData.Any(e2 => e2.Format == "PDF")
+                )
+                .ToArray();
         }
     }
 }
diff --git a/src/Tools.PdfProvider/SimplePdfReader.cs b/src/Tools.PdfProvider/SimplePdfReader.cs
new file mode 100644
index 0000000..a429748
--- /dev/null
+++ b/src/Tools.PdfProvider/SimplePdfReader.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Text;
+using System.IO;
+using System.Threading.Tasks;
+
+using iText.Kernel.Pdf;
+using iText.Kernel.Pdf.Canvas.Parser;
+using iText.Kernel.Pdf.Canvas.Parser.Listener;
+
+namespace Tools.PdfProvider
+{
+    public class SimplePdfReader
+    {
+        public Task<string> ReadPdfAsync(Stream stream)
+        {
+            StringBuilder text = new StringBuilder();
+
+            using (PdfReader iTextReader = new PdfReader(stream))
+            using (PdfDocument pdfDoc = new PdfDocument(iTextReader))
+            {
+                int numberofpages = pdfDoc.GetNumberOfPages();
+                for (int page = 1; page <= numberofpages; page++)
+                {
+                    ITextExtractionStrategy strategy = new SimpleTextExtractionStrategy();
+                    string currentText = PdfTextExtractor.GetTextFromPage(
+                        pdfDoc.GetPage(page), 
+                        strategy
+                        );
+
+                    //currentText = Encoding.UTF8.GetString(
+                    //    ASCIIEncoding.Convert(
+                    //        Encoding.Default,
+                    //        Encoding.UTF8,
+                    //        Encoding.Default.GetBytes(currentText)
+                    //        )
+                    //    );
+                    text.Append(currentText);
+                }
+            }
+
+            return Task.FromResult(
+                text.ToString()
+                );
+        }
+    }
+}
diff --git a/src/Tools.PdfProvider/Tools.PdfProvider.csproj b/src/Tools.PdfProvider/Tools.PdfProvider.csproj
new file mode 100644
index 0000000..991bc59
--- /dev/null
+++ b/src/Tools.PdfProvider/Tools.PdfProvider.csproj
@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net5.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="itext7" Version="7.1.16" />
+  </ItemGroup>
+
+</Project>