-
Notifications
You must be signed in to change notification settings - Fork 1
/
CommandCompress.cs
172 lines (159 loc) · 7 KB
/
CommandCompress.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace de.intronik.backup
{
[Command("replace", "compress",
Description = "replace existing folder structure by it's hash link",
Syntax = "folderOrFile1 folderOrFile2 folderOrFileN",
MinParameterCount = 1
)]
public class CommandCompress : CommandBase
{
#region private fields
/// <summary>
/// stores the position of the 'd' or 'f' character in the hash path string
/// </summary>
int typeIndex;
long duplicateFiles;
long duplicateBytes;
long duplicateFolders;
#endregion
#region private methods
HashEntry ReplaceByLink(FileSystemInfo fileSystemInfo, HashEntry hash, int level)
{
return hash.IsDirectory ?
ReplaceByLink(fileSystemInfo as DirectoryInfo, hash, level) :
ReplaceByLink(fileSystemInfo as FileInfo, hash, level);
}
HashEntry ReplaceByLink(DirectoryInfo dirInfo, HashEntry hash, int level)
{
var hashPath = this.GetFullHashPath(hash);
// either move current file to hash folder or delete it
dirInfo.Attributes = FileAttributes.Normal;
if (Directory.Exists(hashPath)) {
this.duplicateFolders++;
dirInfo.Delete(true);
} else {
FileRoutines.MoveDirectory(dirInfo.FullName, hashPath);
}
// create link
CreateLink(dirInfo.FullName, hash, level);
// return new hash (that free's up memory)
return hash;
}
HashEntry ReplaceByLink(FileInfo fileInfo, HashEntry hash, int level)
{
var hashPath = this.GetFullHashPath(hash);
// either move current file to hash folder or delete it
fileInfo.Attributes = FileAttributes.Normal;
if (File.Exists(hashPath))
{
this.duplicateFiles++;
this.duplicateBytes += fileInfo.Length;
fileInfo.Delete();
}
else
File.Move(fileInfo.FullName, hashPath);// otherwise the fileInfo is updated
// create link
CreateLink(fileInfo.FullName, hash, level);
// return new hash (that free's up memory)
return hash;
}
HashEntry Handle(FileSystemInfo fileSystemInfo, int level)
{
return (fileSystemInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory ?
Handle(fileSystemInfo as DirectoryInfo, level) :
Handle(fileSystemInfo as FileInfo, level);
}
HashEntry Handle(FileInfo fileInfo, int level)
{
// first check if the file is already a symbolic hash link, if so we are done!
var hashEntryPath = Win32.GetJunctionTarget(fileInfo.FullName);
if (hashEntryPath != null)
{
if (hashEntryPath.StartsWith(this.HashFolder, StringComparison.InvariantCultureIgnoreCase))
return new PathHashEntry(hashEntryPath, this.typeIndex);
Console.WriteLine("\"{0}\" does not target hash folder \"{1}\" and it is processed like a normal file!", hashEntryPath, HashFolder);
}
// build hash
var hash = new FileHashEntry(fileInfo, this.ProcessHashAction);
// replace by symlink
return new PathHashEntry(ReplaceByLink(fileInfo, hash, level));
}
HashEntry Handle(DirectoryInfo dirInfo, int level)
{
// first check if the file is already a symbolic hash link, if so we are done!
var hashEntryPath = Win32.GetJunctionTarget(dirInfo.FullName);
if (hashEntryPath != null)
{
if (hashEntryPath.StartsWith(this.HashFolder, StringComparison.InvariantCultureIgnoreCase))
return new PathHashEntry(hashEntryPath, this.typeIndex);
Console.WriteLine("\"{0}\" does not target hash folder \"{1}\" and it is processed like a normal folder!", hashEntryPath, HashFolder);
}
// build hash
var subItems = dirInfo.GetFileSystemInfos();
var hash = new DirectoryHashEntry(dirInfo.Name, subItems.Length);
foreach (var subItem in subItems)
{
// handle sub item
var subHash = Handle(subItem, level + 1);
// add it
hash.Entries.Add(subItem.Name, subHash);
}
var hashPath = this.GetFullHashPath(hash);
// replace by symlink
return new PathHashEntry(ReplaceByLink(dirInfo, hash, level));
}
#endregion private methods
#region protected methods
protected override void SetParameters(string[] value)
{
base.SetParameters(value);
// determine default hash folder from the first folder (make sure destination path is rooted)
var firstFolder = this.Parameters.First();
if (!Path.IsPathRooted(firstFolder))
firstFolder = Path.GetFullPath(Path.Combine(Path.GetPathRoot(Directory.GetCurrentDirectory()), firstFolder));
// check for empty hash directories -> use default if none was given
if (string.IsNullOrEmpty(this.HashFolder))
this.HashFolder = GetDefaultHashDir(firstFolder);
}
protected override int DoOperation()
{
// transform to FileSystemInfo
var sources = this.Parameters
.Select(p => Directory.Exists(p) ? (FileSystemInfo)new DirectoryInfo(p) : (FileSystemInfo)new FileInfo(p));
// prep hash folder
PrepareHashDirectory();
this.typeIndex = this.HashFolder.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Length + 1;
foreach (var fi in sources)
{
// ignore non existing items
if (!fi.Exists)
{
HandleError(fi, new FileNotFoundException("Ignoring non existing file/folder", fi.FullName));
continue;
}
// ignore items that do not have the same root as the hash folder
if (String.Compare(Path.GetPathRoot(fi.FullName), Path.GetPathRoot(this.HashFolder), true) != 0)
{
HandleError(fi, new InvalidOperationException("Folder is not on the same drive as the hash folder"));
continue;
}
ReplaceByLink(fi, Handle(fi, 1), 1);
}
return 0;
}
protected override void ShowStatistics()
{
base.ShowStatistics();
print("Duplicate Files", this.duplicateFiles);
print("Duplicate Bytes", FormatBytes(duplicateBytes));
print("Duplicate Folders", duplicateFolders);
}
#endregion
}
}