448 lines
16 KiB
C#
448 lines
16 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
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>> OrganizationFolders { 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);
|
|
OrganizationFolders.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);
|
|
|
|
OrganizationFolders[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);
|
|
OrganizationFolders.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);
|
|
|
|
OrganizationFolders[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 (OrganizationFolders.ContainsKey(folder.FullPath)) OrganizationFolders.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.OrganizationFolders)
|
|
{
|
|
if (!OrganizationFolders.ContainsKey(folder.Key) && folder.Key != Root.FullPath)
|
|
{
|
|
Root.AddDirectory(snapshot.Directories[folder.Key]);
|
|
OrganizationFolders.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;
|
|
}
|
|
|
|
OrganizationFolders[newFile.OrganizationalFolder.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.OrganizationFolders)
|
|
{
|
|
if (!a.OrganizationFolders.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,
|
|
NewOrganizationalFolders = newOrgFolders
|
|
};
|
|
}
|
|
|
|
public struct Duplicate
|
|
{
|
|
public DB.DBFileSnapshot 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 class FileSnapshot
|
|
{
|
|
public Folder OrganizationalFolder { 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)
|
|
{
|
|
OrganizationalFolder = 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)
|
|
{
|
|
OrganizationalFolder = 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> NewOrganizationalFolders { get; set; }
|
|
}
|
|
}
|