Files
data_organizer/DataOrganizer/Snapshot.cs
T

542 lines
20 KiB
C#

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;
namespace DataOrganizer
{
class Snapshot
{
public Folder Root { get; private set; }
public Dictionary<string, FileSnapshot> Files { get; private set; } = new Dictionary<string, FileSnapshot>();
public Dictionary<string, Folder> Directories { get; private set; } = new Dictionary<string, Folder>();
public Dictionary<string, Duplicate> FileDuplicates { get; private set; } = new Dictionary<string, Duplicate>();
//Contains files associated with particular, first level folders.
//So if the root folder group is X:\Anime and we have a folder path of x:\Anime\Death Note, then indexing this dictonary
//with the text "Death Note" would return all files in that folder.
public Dictionary<string, List<FileSnapshot>> TopLevelFolders { get; private set; } = new Dictionary<string, List<FileSnapshot>>();
public Snapshot(List<DB.DBFileSnapshot> fileRecords, DB.FolderGroup folderGroup)
{
Root = new Folder(folderGroup.Name, folderGroup.Path, null);
Directories.Add(Root.FullPath, Root);
if (folderGroup.Type == DB.OrganizationType.SingleLevel)
ProcessFilesAsOneLevel(fileRecords, folderGroup);
//else if (folderGroup.Type == DB.OrganizationType.Flat)
// ProcessFilesAsFlat(fileRecords, folderGroup);
}
public Snapshot(List<FileInfo> files, DB.FolderGroup folderGroup)
{
Root = new Folder(folderGroup.Name, folderGroup.Path, null);
Directories.Add(Root.FullPath, Root);
if (folderGroup.Type == DB.OrganizationType.SingleLevel)
ProcessFilesAsOneLevel(files, folderGroup);
}
private void ProcessFilesAsOneLevel(List<DB.DBFileSnapshot> files, DB.FolderGroup folderGroup)
{
Folder firstLevelFolder = null;
foreach (var file in files)
{
var folders = file.PathNoRoot.Split(new char[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
var orgFolder = Path.Combine(folderGroup.Path, folders[0]);
var currentPath = Path.Combine(folderGroup.Path, folders[0]);
if(firstLevelFolder == null || firstLevelFolder.Name != folders[0])
{
firstLevelFolder = new Folder(folders[0], Path.Combine(folderGroup.Path, folders[0]), Root);
Directories.Add(orgFolder, firstLevelFolder);
TopLevelFolders.Add(orgFolder, new List<FileSnapshot>());
Root.AddDirectory(firstLevelFolder);
}
for(var i = 1; i < folders.Length - 1; i++)
{
var parentFolder = currentPath;
currentPath = Path.Combine(currentPath, folders[i]);
if(!Directories.ContainsKey(currentPath))
{
var newFolder = new Folder(folders[i], currentPath, Directories[parentFolder]);
Directories.Add(currentPath, newFolder);
Directories[parentFolder].AddDirectory(newFolder);
}
}
//var duplicates = OrganizationFolders[orgFolder].Where(x => x.Size == file.Size && x.Id != file.Id);
//foreach(var dup in duplicates)
//{
// if(FileDuplicates.ContainsKey(file.PathNoRoot))
// {
// FileDuplicates[file.PathNoRoot].Duplicates.Add(dup);
// }
// else FileDuplicates.Add(file.PathNoRoot, new Duplicate() { File = file, Duplicates = new List<DB.FileSnapshot>() { dup } });
//}
var snap = new FileSnapshot(file, firstLevelFolder, Directories[currentPath], folderGroup.Path);
TopLevelFolders[orgFolder].Add(snap);
Directories[currentPath].AddFile(snap);
Files.Add(Path.Combine(folderGroup.Path, file.PathNoRoot), snap);
}
}
private void ProcessFilesAsOneLevel(List<FileInfo> files, DB.FolderGroup folderGroup)
{
Folder firstLevelFolder = null;
foreach (var file in files)
{
//Add one to the length to remove the trailing slash in the root path.
var pathNoRoot = file.FullName.Remove(0, folderGroup.Path.Length + 1);
var folders = pathNoRoot.Split(new char[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
var orgFolder = Path.Combine(folderGroup.Path, folders[0]);
var currentPath = Path.Combine(folderGroup.Path, folders[0]);
if (firstLevelFolder == null || firstLevelFolder.Name != folders[0])
{
firstLevelFolder = new Folder(folders[0], Path.Combine(folderGroup.Path, folders[0]), Root);
Directories.Add(orgFolder, firstLevelFolder);
TopLevelFolders.Add(orgFolder, new List<FileSnapshot>());
Root.AddDirectory(firstLevelFolder);
}
for (var i = 1; i < folders.Length - 1; i++)
{
var parentFolder = currentPath;
currentPath = Path.Combine(currentPath, folders[i]);
if (!Directories.ContainsKey(currentPath))
{
var newFolder = new Folder(folders[i], currentPath, Directories[parentFolder]);
Directories.Add(currentPath, newFolder);
Directories[parentFolder].AddDirectory(newFolder);
}
}
//var duplicates = OrganizationFolders[orgFolder].Where(x => x.Size == file.Size && x.Id != file.Id);
//foreach(var dup in duplicates)
//{
// if(FileDuplicates.ContainsKey(file.PathNoRoot))
// {
// FileDuplicates[file.PathNoRoot].Duplicates.Add(dup);
// }
// else FileDuplicates.Add(file.PathNoRoot, new Duplicate() { File = file, Duplicates = new List<DB.FileSnapshot>() { dup } });
//}
var snap = new FileSnapshot(file, firstLevelFolder, Directories[currentPath], folderGroup.Path);
TopLevelFolders[orgFolder].Add(snap);
Directories[currentPath].AddFile(snap);
Files.Add(snap.FullPath, snap);
}
}
private void ProcessFilesAsFlat(List<DB.DBFileSnapshot> files, DB.FolderGroup folderGroup)
{
}
public void DeleteFile(FileSnapshot file)
{
if (Files.ContainsKey(file.FullPath)) Files.Remove(file.FullPath);
Root.DeleteFile(file);
file.ParentFolder.DeleteFile(file);
}
public void DeleteFolder(Folder folder)
{
ClearSubFolders(folder);
}
private void ClearSubFolders(Folder folder)
{
if (Directories.ContainsKey(folder.FullPath)) Directories.Remove(folder.FullPath);
if (TopLevelFolders.ContainsKey(folder.FullPath)) TopLevelFolders.Remove(folder.FullPath);
Root.DeleteFolder(folder);
folder.Parent.DeleteFolder(folder);
foreach (var dir in folder.Folders)
ClearSubFolders(dir.Value);
}
public void MergeSnapshot(Snapshot snapshot)
{
var newFiles = new List<FileSnapshot>();
var existingFiles = Files.Values.Except(snapshot.Files.Values);
foreach(var file in snapshot.Files)
if (!Files.ContainsKey(file.Key)) newFiles.Add(file.Value);
foreach (var folder in snapshot.TopLevelFolders)
{
if (!TopLevelFolders.ContainsKey(folder.Key) && folder.Key != Root.FullPath)
{
Root.AddDirectory(snapshot.Directories[folder.Key]);
TopLevelFolders.Add(folder.Key, new List<FileSnapshot>());
}
}
foreach (var existingFile in existingFiles)
{
if (!snapshot.Files.ContainsKey(existingFile.FullPath))
{
existingFile.Status = FileSnapshot.FileStatus.PendingDelete;
}
else if (existingFile.ModifiedDate != snapshot.Files[existingFile.FullPath].ModifiedDate)
{
existingFile.ModifiedDate = snapshot.Files[existingFile.FullPath].ModifiedDate;
}
}
foreach(var newFile in newFiles)
{
Files.Add(newFile.FullPath, newFile);
Folder folder = newFile.ParentFolder;
while(folder != null)
{
if (!Directories.ContainsKey(folder.FullPath))
Directories.Add(folder.FullPath, folder);
folder = folder.Parent;
}
TopLevelFolders[newFile.TopLevelFolder.FullPath].Add(newFile);
}
}
public SnapshotComparsion Diff(Snapshot snapshot)
{
return Compare(this, snapshot);
}
private SnapshotComparsion Compare(Snapshot a, Snapshot b)
{
var newFiles = new List<FileSnapshot>();
var modifiedFiles = new List<FileSnapshot>();
var deletedFiles = new List<FileSnapshot>();
var existingFiles = a.Files.Values.Except(b.Files.Values);
var newOrgFolders = new List<Folder>();
foreach (var file in b.Files)
if (!a.Files.ContainsKey(file.Key)) newFiles.Add(file.Value);
foreach (var folder in b.TopLevelFolders)
{
if (!a.TopLevelFolders.ContainsKey(folder.Key) && folder.Key != a.Root.FullPath)
{
newOrgFolders.Add(b.Directories[folder.Key]);
}
}
foreach (var existingFile in existingFiles)
{
if (!b.Files.ContainsKey(existingFile.FullPath))
{
deletedFiles.Add(existingFile);
}
else if (existingFile.ModifiedDate != b.Files[existingFile.FullPath].ModifiedDate)
{
modifiedFiles.Add(b.Files[existingFile.FullPath]);
}
}
return new SnapshotComparsion()
{
NewFiles = newFiles,
ModifiedFiles = modifiedFiles,
DeletedFiles = deletedFiles,
NewTopLevelFolders = newOrgFolders
};
}
public static List<SnapshotComparsion.RenamedFile> FindFileRenames(Folder a, Folder b)
{
var renames = new List<SnapshotComparsion.RenamedFile>();
foreach(var file in a.Files.Values)
{
foreach(var f in b.Files.Values)
{
if (file.Size == f.Size && file.CreatedDate == f.CreatedDate && file.ModifiedDate == f.ModifiedDate)
{
if(f.Name != file.Name)
renames.Add(new SnapshotComparsion.RenamedFile() { From = file, To = f });
}
}
}
return renames;
}
public static List<SnapshotComparsion.RenamedFile> FindFileMoves(Folder a, Folder b)
{
var files = new List<SnapshotComparsion.RenamedFile>();
var filesA = a.GetAllFiles();
var filesB = b.GetAllFiles();
foreach(var fileA in filesA)
{
foreach(var fileB in filesB)
{
if(fileB.CreatedDate == fileA.CreatedDate && fileA.ModifiedDate == fileB.ModifiedDate && fileA.Size == fileB.Size)
{
files.Add(new SnapshotComparsion.RenamedFile { From = fileA, To = fileB });
continue;
}
}
}
return files;
}
public static List<Duplicate> FindDuplicates(Folder topLevelFolder)
{
var duplicates = new Dictionary<string, Duplicate>();
var files = topLevelFolder.GetAllFiles();
var fileCount = 0;
var seasonFolderName = new Regex(@"^(?i)season\W+\d+");
var extrasFolderName = new Regex(@"^(?i)(?:extras|specials|movies)+$");
fileCount = files.Count;
for(var i = 0; i < fileCount; i++)
{
var file = files[i];
//if (!duplicates.ContainsKey(file.FullPath)) duplicates.Add(file.FullPath, new Duplicate() { File = file, Duplicates = new List<FileSnapshot>() });
var dups = files.Where(x => x.Size == file.Size && x.FileId != file.FileId).ToList();
var dupCount = dups.Count;
if(dupCount > 0)
if (!duplicates.ContainsKey(file.FullPath)) duplicates.Add(file.FullPath, new Duplicate() { File = file, Duplicates = new List<FileSnapshot>() });
for (var j = 0; j < dupCount; j++)
{
var dup = dups[j];
files.Remove(dup);
duplicates[file.FullPath].Duplicates.Add(dup);
fileCount--;
}
}
return duplicates.Values.ToList();
}
public struct Duplicate
{
public FileSnapshot File;
public List<FileSnapshot> Duplicates;
}
}
public class Folder
{
public string Name { get; private set; }
public string FullPath { get; private set; }
public Folder Parent { get; private set; }
public Dictionary<string, FileSnapshot> Files { get; private set; } = new Dictionary<string, FileSnapshot>();
public Dictionary<string, Folder> Folders { get; private set; } = new Dictionary<string, Folder>();
public bool IsEmpty
{
get { return !(Files.Count > 0 || Folders.Count > 0); }
}
public bool IsRootFolder
{
get { return Parent == null; }
}
public Folder(string name, string fullPath, Folder parent)
{
Name = name;
FullPath = fullPath;
Parent = parent;
}
public void AddFile(FileSnapshot file)
{
Files.Add(file.FullPath, file);
}
public void AddDirectory(Folder folder)
{
Folders.Add(folder.FullPath, folder);
}
public List<FileSnapshot> GetAllPendingChanges()
{
var files = new List<FileSnapshot>();
files.AddRange(Files.Values.Where(x => x.Status != FileSnapshot.FileStatus.None));
foreach (var folder in Folders.Values)
files.AddRange(folder.GetAllPendingChanges());
return files;
}
public void DeleteFile(FileSnapshot file)
{
if (Files.ContainsKey(file.FullPath)) Files.Remove(file.FullPath);
}
public void DeleteFolder(Folder folder)
{
if (Folders.ContainsKey(folder.FullPath)) Folders.Remove(folder.FullPath);
}
public List<FileSnapshot> GetAllFiles()
{
var files = new List<FileSnapshot>();
files.AddRange(Files.Values);
foreach (var dir in Folders.Values)
files.AddRange(dir.GetAllFiles());
return files;
}
}
public class FileSnapshot
{
public Folder TopLevelFolder { get; }
public Folder ParentFolder { get; }
public string PathNoRoot { get; }
private string Root { get; }
private int _FileId { get; set; }
private string _Name { get; set; }
private DateTime _ModifiedDate { get; set; }
public DateTime CreatedDate { get; }
public long Size { get; }
private string _NamingPattern { get; set; }
public FileStatus Status { get; set; }
public int FileId
{
get { return _FileId; }
set
{
//Since this file is getting an ID, that means we've been commited to the database, so change the Status to reflect this.
Status = FileStatus.None;
FileId = value;
}
}
public DateTime ModifiedDate
{
get { return _ModifiedDate; }
set
{
if (Status != FileStatus.PendingInsert) Status = FileStatus.PendingUpdate;
_ModifiedDate = value;
}
}
public string NamingPattern
{
get { return _NamingPattern; }
set
{
if (Status != FileStatus.PendingInsert) Status = FileStatus.PendingUpdate;
_NamingPattern = value;
}
}
public string Name
{
get { return _Name; }
}
public FileSnapshot(DB.DBFileSnapshot file, Folder organizationalFolder, Folder parentFolder, string rootPath)
{
TopLevelFolder = organizationalFolder;
ParentFolder = parentFolder;
var db = new DB();
if (file.PatternId != 0) _NamingPattern = db.GetNamingPatternById(file.PatternId);
PathNoRoot = file.PathNoRoot;
Root = rootPath;
_Name = file.Name;
_ModifiedDate = file.ModifiedDate;
CreatedDate = file.CreationDate;
Size = file.Size;
_FileId = file.Id;
Status = FileStatus.None;
}
public FileSnapshot(FileInfo file, Folder organizationalFolder, Folder parentFolder, string rootPath)
{
TopLevelFolder = organizationalFolder;
ParentFolder = parentFolder;
_Name = file.Name;
//Add one to the length to remove the trailing slash in the root path.
PathNoRoot = file.FullName.Remove(0, rootPath.Length + 1);
Root = rootPath;
_ModifiedDate = file.LastWriteTimeUtc;
CreatedDate = file.CreationTimeUtc;
Size = file.Length;
_FileId = 0;
Status = FileStatus.PendingInsert;
}
public bool HasOwnNamingPattern
{
get
{
return !string.IsNullOrEmpty(NamingPattern);
}
}
public string FullPath
{
get
{
return Path.Combine(Root, PathNoRoot);
}
}
public bool HasPendingChanges
{
get { return Status != FileStatus.None; }
}
public enum FileStatus
{
PendingInsert,
PendingUpdate,
PendingDelete,
Deleted,
None
}
}
public class SnapshotComparsion
{
public List<FileSnapshot> NewFiles { get; set; }
public List<FileSnapshot> ModifiedFiles { get; set; }
public List<FileSnapshot> DeletedFiles { get; set; }
public List<Folder> NewTopLevelFolders { get; set; }
public List<RenamedFile> RenamedFiles { get; set; }
public struct RenamedFile
{
public FileSnapshot From;
public FileSnapshot To;
}
}
}