mirror of
				https://github.com/Kareadita/Kavita.git
				synced 2025-11-04 03:27:05 -05:00 
			
		
		
		
	Implemented the ability to flatten directories, esp useful with nested folders in archives.
This commit is contained in:
		
							parent
							
								
									56e8a0059e
								
							
						
					
					
						commit
						7f404a0ce9
					
				@ -19,8 +19,7 @@ namespace API.Tests
 | 
				
			|||||||
        [InlineData("[Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip", "1")]
 | 
					        [InlineData("[Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip", "1")]
 | 
				
			||||||
        public void ParseVolumeTest(string filename, string expected)
 | 
					        public void ParseVolumeTest(string filename, string expected)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var result = ParseVolume(filename);
 | 
					            Assert.Equal(expected, ParseVolume(filename));
 | 
				
			||||||
            Assert.Equal(expected, result);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [Theory]
 | 
					        [Theory]
 | 
				
			||||||
@ -36,8 +35,7 @@ namespace API.Tests
 | 
				
			|||||||
        [InlineData("Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)", "Akame ga KILL! ZERO")]
 | 
					        [InlineData("Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)", "Akame ga KILL! ZERO")]
 | 
				
			||||||
        public void ParseSeriesTest(string filename, string expected)
 | 
					        public void ParseSeriesTest(string filename, string expected)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var result = ParseSeries(filename);
 | 
					            Assert.Equal(expected, ParseSeries(filename));
 | 
				
			||||||
            Assert.Equal(expected, result);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        [Theory]
 | 
					        [Theory]
 | 
				
			||||||
@ -53,8 +51,7 @@ namespace API.Tests
 | 
				
			|||||||
        [InlineData("Adding volume 1 with File: Ana Satsujin Vol. 1 Ch. 5 - Manga Box (gb).cbz", "5")]
 | 
					        [InlineData("Adding volume 1 with File: Ana Satsujin Vol. 1 Ch. 5 - Manga Box (gb).cbz", "5")]
 | 
				
			||||||
        public void ParseChaptersTest(string filename, string expected)
 | 
					        public void ParseChaptersTest(string filename, string expected)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var result = ParseChapter(filename);
 | 
					            Assert.Equal(expected, ParseChapter(filename));
 | 
				
			||||||
            Assert.Equal(expected, result);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -91,6 +88,7 @@ namespace API.Tests
 | 
				
			|||||||
        [InlineData("test.cbr", true)]
 | 
					        [InlineData("test.cbr", true)]
 | 
				
			||||||
        [InlineData("test.zip", true)]
 | 
					        [InlineData("test.zip", true)]
 | 
				
			||||||
        [InlineData("test.rar", true)]
 | 
					        [InlineData("test.rar", true)]
 | 
				
			||||||
 | 
					        [InlineData("test.rar.!qb", false)]
 | 
				
			||||||
        public void IsArchiveTest(string input, bool expected)
 | 
					        public void IsArchiveTest(string input, bool expected)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Assert.Equal(expected, IsArchive(input));
 | 
					            Assert.Equal(expected, IsArchive(input));
 | 
				
			||||||
 | 
				
			|||||||
@ -30,7 +30,7 @@ namespace API.Comparators
 | 
				
			|||||||
			{
 | 
								{
 | 
				
			||||||
				bool c1 = Char.IsDigit(s1, i1);
 | 
									bool c1 = Char.IsDigit(s1, i1);
 | 
				
			||||||
				bool c2 = Char.IsDigit(s2, i2);
 | 
									bool c2 = Char.IsDigit(s2, i2);
 | 
				
			||||||
				var r = 0; // temp result
 | 
									int r; // temp result
 | 
				
			||||||
				if(!c1 && !c2)
 | 
									if(!c1 && !c2)
 | 
				
			||||||
				{
 | 
									{
 | 
				
			||||||
					bool letter1 = Char.IsLetter(s1, i1);
 | 
										bool letter1 = Char.IsLetter(s1, i1);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,5 @@
 | 
				
			|||||||
using System.Linq;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
using API.DTOs;
 | 
					using API.DTOs;
 | 
				
			||||||
using API.Entities;
 | 
					 | 
				
			||||||
using API.Interfaces;
 | 
					using API.Interfaces;
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,65 @@
 | 
				
			|||||||
namespace API.Extensions
 | 
					using System;
 | 
				
			||||||
 | 
					using System.IO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace API.Extensions
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public static class DirectoryInfoExtensions
 | 
					    public static class DirectoryInfoExtensions
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        public static void Empty(this System.IO.DirectoryInfo directory)
 | 
					        public static void Empty(this DirectoryInfo directory)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            foreach(System.IO.FileInfo file in directory.EnumerateFiles()) file.Delete();
 | 
					            foreach(FileInfo file in directory.EnumerateFiles()) file.Delete();
 | 
				
			||||||
            foreach(System.IO.DirectoryInfo subDirectory in directory.EnumerateDirectories()) subDirectory.Delete(true);
 | 
					            foreach(DirectoryInfo subDirectory in directory.EnumerateDirectories()) subDirectory.Delete(true);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Flattens all files in subfolders to the passed directory recursively.
 | 
				
			||||||
 | 
					        /// 
 | 
				
			||||||
 | 
					        /// 
 | 
				
			||||||
 | 
					        /// foo<para />
 | 
				
			||||||
 | 
					        /// ├── 1.txt<para />
 | 
				
			||||||
 | 
					        /// ├── 2.txt<para />
 | 
				
			||||||
 | 
					        /// ├── 3.txt<para />
 | 
				
			||||||
 | 
					        /// ├── 4.txt<para />
 | 
				
			||||||
 | 
					        /// └── bar<para />
 | 
				
			||||||
 | 
					        ///     ├── 1.txt<para />
 | 
				
			||||||
 | 
					        ///     ├── 2.txt<para />
 | 
				
			||||||
 | 
					        ///     └── 5.txt<para />
 | 
				
			||||||
 | 
					        /// 
 | 
				
			||||||
 | 
					        /// becomes:<para />
 | 
				
			||||||
 | 
					        /// foo<para />
 | 
				
			||||||
 | 
					        /// ├── 1.txt<para />
 | 
				
			||||||
 | 
					        /// ├── 2.txt<para />
 | 
				
			||||||
 | 
					        /// ├── 3.txt<para />
 | 
				
			||||||
 | 
					        /// ├── 4.txt<para />
 | 
				
			||||||
 | 
					        ///     ├── bar_1.txt<para />
 | 
				
			||||||
 | 
					        ///     ├── bar_2.txt<para />
 | 
				
			||||||
 | 
					        ///     └── bar_5.txt<para />
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="directory"></param>
 | 
				
			||||||
 | 
					        public static void Flatten(this DirectoryInfo directory)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            FlattenDirectory(directory, directory);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static void FlattenDirectory(DirectoryInfo root, DirectoryInfo directory)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (!root.FullName.Equals(directory.FullName)) // I might be able to replace this with root === directory
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                foreach (var file in directory.EnumerateFiles())
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    if (file.Directory == null) continue;
 | 
				
			||||||
 | 
					                    var newName = $"{file.Directory.Name}_{file.Name}";
 | 
				
			||||||
 | 
					                    var newPath = Path.Join(root.FullName, newName);
 | 
				
			||||||
 | 
					                    Console.WriteLine($"Renaming/Moving file to: {newPath}");
 | 
				
			||||||
 | 
					                    file.MoveTo(newPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            foreach (var subDirectory in directory.EnumerateDirectories())
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                FlattenDirectory(root, subDirectory);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -4,7 +4,6 @@ using API.DTOs;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace API.Interfaces
 | 
					namespace API.Interfaces
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    // TODO: Refactor this into IDiskService to encapsulate all disk based IO
 | 
					 | 
				
			||||||
    public interface IDirectoryService
 | 
					    public interface IDirectoryService
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
 | 
				
			|||||||
@ -42,7 +42,7 @@ namespace API.Services
 | 
				
			|||||||
            foreach (var file in volume.Files)
 | 
					            foreach (var file in volume.Files)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var extractPath = GetVolumeCachePath(volumeId, file);
 | 
					                var extractPath = GetVolumeCachePath(volumeId, file);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
                _directoryService.ExtractArchive(file.FilePath, extractPath);
 | 
					                _directoryService.ExtractArchive(file.FilePath, extractPath);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -137,6 +137,7 @@ namespace API.Services
 | 
				
			|||||||
          {
 | 
					          {
 | 
				
			||||||
             FilePath = info.FullFilePath,
 | 
					             FilePath = info.FullFilePath,
 | 
				
			||||||
             Chapter = chapter,
 | 
					             Chapter = chapter,
 | 
				
			||||||
 | 
					             Format = info.Format,
 | 
				
			||||||
             NumberOfPages = GetNumberOfPagesFromArchive(info.FullFilePath)
 | 
					             NumberOfPages = GetNumberOfPagesFromArchive(info.FullFilePath)
 | 
				
			||||||
          };
 | 
					          };
 | 
				
			||||||
       }
 | 
					       }
 | 
				
			||||||
@ -284,7 +285,13 @@ namespace API.Services
 | 
				
			|||||||
           return Path.Join(Directory.GetCurrentDirectory(), $"../cache/{volumeId}/");
 | 
					           return Path.Join(Directory.GetCurrentDirectory(), $"../cache/{volumeId}/");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string ExtractArchive(string archivePath, int volumeId)
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// TODO: Delete this method
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="archivePath"></param>
 | 
				
			||||||
 | 
					        /// <param name="volumeId"></param>
 | 
				
			||||||
 | 
					        /// <returns></returns>
 | 
				
			||||||
 | 
					        private string ExtractArchive(string archivePath, int volumeId)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
           if (!File.Exists(archivePath) || !Parser.Parser.IsArchive(archivePath))
 | 
					           if (!File.Exists(archivePath) || !Parser.Parser.IsArchive(archivePath))
 | 
				
			||||||
           {
 | 
					           {
 | 
				
			||||||
@ -302,10 +309,18 @@ namespace API.Services
 | 
				
			|||||||
           
 | 
					           
 | 
				
			||||||
           using ZipArchive archive = ZipFile.OpenRead(archivePath);
 | 
					           using ZipArchive archive = ZipFile.OpenRead(archivePath);
 | 
				
			||||||
           
 | 
					           
 | 
				
			||||||
           if (!archive.HasFiles()) return "";
 | 
					           // TODO: Throw error if we couldn't extract
 | 
				
			||||||
            
 | 
					           var needsFlattening = archive.Entries.Count > 0 && !Path.HasExtension(archive.Entries.ElementAt(0).FullName);
 | 
				
			||||||
 | 
					           if (!archive.HasFiles() && !needsFlattening) return "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
           archive.ExtractToDirectory(extractPath);
 | 
					           archive.ExtractToDirectory(extractPath);
 | 
				
			||||||
           _logger.LogInformation($"Extracting archive to {extractPath}");
 | 
					           _logger.LogInformation($"Extracting archive to {extractPath}");
 | 
				
			||||||
 | 
					           
 | 
				
			||||||
 | 
					           if (needsFlattening)
 | 
				
			||||||
 | 
					           {
 | 
				
			||||||
 | 
					              _logger.LogInformation("Extracted archive is nested in root folder, flattening...");
 | 
				
			||||||
 | 
					              new DirectoryInfo(extractPath).Flatten();
 | 
				
			||||||
 | 
					           }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
           return extractPath;
 | 
					           return extractPath;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -325,12 +340,18 @@ namespace API.Services
 | 
				
			|||||||
           }
 | 
					           }
 | 
				
			||||||
           
 | 
					           
 | 
				
			||||||
           using ZipArchive archive = ZipFile.OpenRead(archivePath);
 | 
					           using ZipArchive archive = ZipFile.OpenRead(archivePath);
 | 
				
			||||||
           
 | 
					           // TODO: Throw error if we couldn't extract
 | 
				
			||||||
           if (!archive.HasFiles()) return "";
 | 
					           var needsFlattening = archive.Entries.Count > 0 && !Path.HasExtension(archive.Entries.ElementAt(0).FullName);
 | 
				
			||||||
 | 
					           if (!archive.HasFiles() && !needsFlattening) return "";
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
           archive.ExtractToDirectory(extractPath);
 | 
					           archive.ExtractToDirectory(extractPath);
 | 
				
			||||||
           _logger.LogDebug($"Extracting archive to {extractPath}");
 | 
					           _logger.LogDebug($"Extracting archive to {extractPath}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           if (!needsFlattening) return extractPath;
 | 
				
			||||||
 | 
					           
 | 
				
			||||||
 | 
					           _logger.LogInformation("Extracted archive is nested in root folder, flattening...");
 | 
				
			||||||
 | 
					           new DirectoryInfo(extractPath).Flatten();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
           return extractPath;
 | 
					           return extractPath;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -343,7 +364,6 @@ namespace API.Services
 | 
				
			|||||||
           }
 | 
					           }
 | 
				
			||||||
           
 | 
					           
 | 
				
			||||||
           using ZipArchive archive = ZipFile.OpenRead(archivePath);
 | 
					           using ZipArchive archive = ZipFile.OpenRead(archivePath);
 | 
				
			||||||
           Console.WriteLine();
 | 
					 | 
				
			||||||
           return archive.Entries.Count(e => Parser.Parser.IsImage(e.FullName));
 | 
					           return archive.Entries.Count(e => Parser.Parser.IsImage(e.FullName));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user