Revision 1caef52e
b/trunk/Pithos.Client.WPF/NativeMethods.cs | ||
---|---|---|
1 |
using System; |
|
2 |
using System.Runtime.InteropServices; |
|
3 |
|
|
4 |
namespace Pithos.Client.WPF |
|
5 |
{ |
|
6 |
#region Enums & Structs |
|
7 |
|
|
8 |
#region enum HChangeNotifyEventID |
|
9 |
/// <summary> |
|
10 |
/// Describes the event that has occurred. |
|
11 |
/// Typically, only one event is specified at a time. |
|
12 |
/// If more than one event is specified, the values contained |
|
13 |
/// in the <i>dwItem1</i> and <i>dwItem2</i> |
|
14 |
/// parameters must be the same, respectively, for all specified events. |
|
15 |
/// This parameter can be one or more of the following values. |
|
16 |
/// </summary> |
|
17 |
/// <remarks> |
|
18 |
/// <para><b>Windows NT/2000/XP:</b> <i>dwItem2</i> contains the index |
|
19 |
/// in the system image list that has changed. |
|
20 |
/// <i>dwItem1</i> is not used and should be <see langword="null"/>.</para> |
|
21 |
/// <para><b>Windows 95/98:</b> <i>dwItem1</i> contains the index |
|
22 |
/// in the system image list that has changed. |
|
23 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.</para> |
|
24 |
/// </remarks> |
|
25 |
[Flags] |
|
26 |
enum HChangeNotifyEventID |
|
27 |
{ |
|
28 |
/// <summary> |
|
29 |
/// All events have occurred. |
|
30 |
/// </summary> |
|
31 |
SHCNE_ALLEVENTS = 0x7FFFFFFF, |
|
32 |
|
|
33 |
/// <summary> |
|
34 |
/// A file type association has changed. <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> |
|
35 |
/// must be specified in the <i>uFlags</i> parameter. |
|
36 |
/// <i>dwItem1</i> and <i>dwItem2</i> are not used and must be <see langword="null"/>. |
|
37 |
/// </summary> |
|
38 |
SHCNE_ASSOCCHANGED = 0x08000000, |
|
39 |
|
|
40 |
/// <summary> |
|
41 |
/// The attributes of an item or folder have changed. |
|
42 |
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
43 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
44 |
/// <i>dwItem1</i> contains the item or folder that has changed. |
|
45 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>. |
|
46 |
/// </summary> |
|
47 |
SHCNE_ATTRIBUTES = 0x00000800, |
|
48 |
|
|
49 |
/// <summary> |
|
50 |
/// A nonfolder item has been created. |
|
51 |
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
52 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
53 |
/// <i>dwItem1</i> contains the item that was created. |
|
54 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>. |
|
55 |
/// </summary> |
|
56 |
SHCNE_CREATE = 0x00000002, |
|
57 |
|
|
58 |
/// <summary> |
|
59 |
/// A nonfolder item has been deleted. |
|
60 |
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
61 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
62 |
/// <i>dwItem1</i> contains the item that was deleted. |
|
63 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>. |
|
64 |
/// </summary> |
|
65 |
SHCNE_DELETE = 0x00000004, |
|
66 |
|
|
67 |
/// <summary> |
|
68 |
/// A drive has been added. |
|
69 |
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
70 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
71 |
/// <i>dwItem1</i> contains the root of the drive that was added. |
|
72 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>. |
|
73 |
/// </summary> |
|
74 |
SHCNE_DRIVEADD = 0x00000100, |
|
75 |
|
|
76 |
/// <summary> |
|
77 |
/// A drive has been added and the Shell should create a new window for the drive. |
|
78 |
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
79 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
80 |
/// <i>dwItem1</i> contains the root of the drive that was added. |
|
81 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>. |
|
82 |
/// </summary> |
|
83 |
SHCNE_DRIVEADDGUI = 0x00010000, |
|
84 |
|
|
85 |
/// <summary> |
|
86 |
/// A drive has been removed. <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
87 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
88 |
/// <i>dwItem1</i> contains the root of the drive that was removed. |
|
89 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>. |
|
90 |
/// </summary> |
|
91 |
SHCNE_DRIVEREMOVED = 0x00000080, |
|
92 |
|
|
93 |
/// <summary> |
|
94 |
/// Not currently used. |
|
95 |
/// </summary> |
|
96 |
SHCNE_EXTENDED_EVENT = 0x04000000, |
|
97 |
|
|
98 |
/// <summary> |
|
99 |
/// The amount of free space on a drive has changed. |
|
100 |
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
101 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
102 |
/// <i>dwItem1</i> contains the root of the drive on which the free space changed. |
|
103 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>. |
|
104 |
/// </summary> |
|
105 |
SHCNE_FREESPACE = 0x00040000, |
|
106 |
|
|
107 |
/// <summary> |
|
108 |
/// Storage media has been inserted into a drive. |
|
109 |
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
110 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
111 |
/// <i>dwItem1</i> contains the root of the drive that contains the new media. |
|
112 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>. |
|
113 |
/// </summary> |
|
114 |
SHCNE_MEDIAINSERTED = 0x00000020, |
|
115 |
|
|
116 |
/// <summary> |
|
117 |
/// Storage media has been removed from a drive. |
|
118 |
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
119 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
120 |
/// <i>dwItem1</i> contains the root of the drive from which the media was removed. |
|
121 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>. |
|
122 |
/// </summary> |
|
123 |
SHCNE_MEDIAREMOVED = 0x00000040, |
|
124 |
|
|
125 |
/// <summary> |
|
126 |
/// A folder has been created. <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> |
|
127 |
/// or <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
128 |
/// <i>dwItem1</i> contains the folder that was created. |
|
129 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>. |
|
130 |
/// </summary> |
|
131 |
SHCNE_MKDIR = 0x00000008, |
|
132 |
|
|
133 |
/// <summary> |
|
134 |
/// A folder on the local computer is being shared via the network. |
|
135 |
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
136 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
137 |
/// <i>dwItem1</i> contains the folder that is being shared. |
|
138 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>. |
|
139 |
/// </summary> |
|
140 |
SHCNE_NETSHARE = 0x00000200, |
|
141 |
|
|
142 |
/// <summary> |
|
143 |
/// A folder on the local computer is no longer being shared via the network. |
|
144 |
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
145 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
146 |
/// <i>dwItem1</i> contains the folder that is no longer being shared. |
|
147 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>. |
|
148 |
/// </summary> |
|
149 |
SHCNE_NETUNSHARE = 0x00000400, |
|
150 |
|
|
151 |
/// <summary> |
|
152 |
/// The name of a folder has changed. |
|
153 |
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
154 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
155 |
/// <i>dwItem1</i> contains the previous pointer to an item identifier list (PIDL) or name of the folder. |
|
156 |
/// <i>dwItem2</i> contains the new PIDL or name of the folder. |
|
157 |
/// </summary> |
|
158 |
SHCNE_RENAMEFOLDER = 0x00020000, |
|
159 |
|
|
160 |
/// <summary> |
|
161 |
/// The name of a nonfolder item has changed. |
|
162 |
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
163 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
164 |
/// <i>dwItem1</i> contains the previous PIDL or name of the item. |
|
165 |
/// <i>dwItem2</i> contains the new PIDL or name of the item. |
|
166 |
/// </summary> |
|
167 |
SHCNE_RENAMEITEM = 0x00000001, |
|
168 |
|
|
169 |
/// <summary> |
|
170 |
/// A folder has been removed. |
|
171 |
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
172 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
173 |
/// <i>dwItem1</i> contains the folder that was removed. |
|
174 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>. |
|
175 |
/// </summary> |
|
176 |
SHCNE_RMDIR = 0x00000010, |
|
177 |
|
|
178 |
/// <summary> |
|
179 |
/// The computer has disconnected from a server. |
|
180 |
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
181 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
182 |
/// <i>dwItem1</i> contains the server from which the computer was disconnected. |
|
183 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>. |
|
184 |
/// </summary> |
|
185 |
SHCNE_SERVERDISCONNECT = 0x00004000, |
|
186 |
|
|
187 |
/// <summary> |
|
188 |
/// The contents of an existing folder have changed, |
|
189 |
/// but the folder still exists and has not been renamed. |
|
190 |
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or |
|
191 |
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>. |
|
192 |
/// <i>dwItem1</i> contains the folder that has changed. |
|
193 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>. |
|
194 |
/// If a folder has been created, deleted, or renamed, use SHCNE_MKDIR, SHCNE_RMDIR, or |
|
195 |
/// SHCNE_RENAMEFOLDER, respectively, instead. |
|
196 |
/// </summary> |
|
197 |
SHCNE_UPDATEDIR = 0x00001000, |
|
198 |
|
|
199 |
SHCNE_UPDATEITEM = 0x00002000, |
|
200 |
|
|
201 |
/// <summary> |
|
202 |
/// An image in the system image list has changed. |
|
203 |
/// <see cref="HChangeNotifyFlags.SHCNF_DWORD"/> must be specified in <i>uFlags</i>. |
|
204 |
/// </summary> |
|
205 |
SHCNE_UPDATEIMAGE = 0x00008000, |
|
206 |
|
|
207 |
} |
|
208 |
#endregion // enum HChangeNotifyEventID |
|
209 |
|
|
210 |
#region public enum HChangeNotifyFlags |
|
211 |
/// <summary> |
|
212 |
/// Flags that indicate the meaning of the <i>dwItem1</i> and <i>dwItem2</i> parameters. |
|
213 |
/// The uFlags parameter must be one of the following values. |
|
214 |
/// </summary> |
|
215 |
[Flags] |
|
216 |
public enum HChangeNotifyFlags |
|
217 |
{ |
|
218 |
/// <summary> |
|
219 |
/// The <i>dwItem1</i> and <i>dwItem2</i> parameters are DWORD values. |
|
220 |
/// </summary> |
|
221 |
SHCNF_DWORD = 0x0003, |
|
222 |
/// <summary> |
|
223 |
/// <i>dwItem1</i> and <i>dwItem2</i> are the addresses of ITEMIDLIST structures that |
|
224 |
/// represent the item(s) affected by the change. |
|
225 |
/// Each ITEMIDLIST must be relative to the desktop folder. |
|
226 |
/// </summary> |
|
227 |
SHCNF_IDLIST = 0x0000, |
|
228 |
/// <summary> |
|
229 |
/// <i>dwItem1</i> and <i>dwItem2</i> are the addresses of null-terminated strings of |
|
230 |
/// maximum length MAX_PATH that contain the full path names |
|
231 |
/// of the items affected by the change. |
|
232 |
/// </summary> |
|
233 |
SHCNF_PATHA = 0x0001, |
|
234 |
/// <summary> |
|
235 |
/// <i>dwItem1</i> and <i>dwItem2</i> are the addresses of null-terminated strings of |
|
236 |
/// maximum length MAX_PATH that contain the full path names |
|
237 |
/// of the items affected by the change. |
|
238 |
/// </summary> |
|
239 |
SHCNF_PATHW = 0x0005, |
|
240 |
/// <summary> |
|
241 |
/// <i>dwItem1</i> and <i>dwItem2</i> are the addresses of null-terminated strings that |
|
242 |
/// represent the friendly names of the printer(s) affected by the change. |
|
243 |
/// </summary> |
|
244 |
SHCNF_PRINTERA = 0x0002, |
|
245 |
/// <summary> |
|
246 |
/// <i>dwItem1</i> and <i>dwItem2</i> are the addresses of null-terminated strings that |
|
247 |
/// represent the friendly names of the printer(s) affected by the change. |
|
248 |
/// </summary> |
|
249 |
SHCNF_PRINTERW = 0x0006, |
|
250 |
/// <summary> |
|
251 |
/// The function should not return until the notification |
|
252 |
/// has been delivered to all affected components. |
|
253 |
/// As this flag modifies other data-type flags, it cannot by used by itself. |
|
254 |
/// </summary> |
|
255 |
SHCNF_FLUSH = 0x1000, |
|
256 |
/// <summary> |
|
257 |
/// The function should begin delivering notifications to all affected components |
|
258 |
/// but should return as soon as the notification process has begun. |
|
259 |
/// As this flag modifies other data-type flags, it cannot by used by itself. |
|
260 |
/// </summary> |
|
261 |
SHCNF_FLUSHNOWAIT = 0x2000 |
|
262 |
} |
|
263 |
#endregion // enum HChangeNotifyFlags |
|
264 |
|
|
265 |
|
|
266 |
#endregion |
|
267 |
|
|
268 |
|
|
269 |
internal static class NativeMethods |
|
270 |
{ |
|
271 |
[DllImport("shell32.dll")] |
|
272 |
public static extern void SHChangeNotify(HChangeNotifyEventID wEventId, |
|
273 |
HChangeNotifyFlags uFlags, |
|
274 |
IntPtr dwItem1, |
|
275 |
IntPtr dwItem2); |
|
276 |
|
|
277 |
} |
|
278 |
} |
/dev/null | ||
---|---|---|
1 |
using System; |
|
2 |
using System.Collections.Generic; |
|
3 |
using System.IO; |
|
4 |
using System.Linq; |
|
5 |
using System.Security.Cryptography; |
|
6 |
using System.Text; |
|
7 |
using System.Threading; |
|
8 |
using System.Threading.Tasks; |
|
9 |
using NUnit.Framework; |
|
10 |
using Newtonsoft.Json; |
|
11 |
|
|
12 |
namespace Pithos.Core.Test |
|
13 |
{ |
|
14 |
[TestFixture] |
|
15 |
public class SignatureTest |
|
16 |
{ |
|
17 |
|
|
18 |
[Test] |
|
19 |
public void TestCreate() |
|
20 |
{ |
|
21 |
var hasher = HashAlgorithm.Create("sha256"); |
|
22 |
Assert.IsNotNull(hasher); |
|
23 |
} |
|
24 |
|
|
25 |
[Test] |
|
26 |
public void TestHashmapCreation() |
|
27 |
{ |
|
28 |
var file = "e:\\pithos\\vlc-1.1.11-win32.exe"; |
|
29 |
|
|
30 |
decimal blockSize = 4*1024*1024; |
|
31 |
|
|
32 |
var fileSize = new FileInfo(file).Length; |
|
33 |
var numBlocks = decimal.Ceiling(fileSize/blockSize); |
|
34 |
|
|
35 |
var md5 = Signature.CalculateMD5(file); |
|
36 |
|
|
37 |
var hash1 = Signature.CalculateTreeHashAsync(file, (int) blockSize,"sha256").Result; |
|
38 |
Assert.IsNotNull(hash1.Hashes); |
|
39 |
Assert.AreEqual(numBlocks, hash1.Hashes.Count()); |
|
40 |
|
|
41 |
var topHash = hash1.TopHash; |
|
42 |
var hashString = BytesToStr(topHash); |
|
43 |
|
|
44 |
var stringHashes = (from hash in hash1.Hashes |
|
45 |
select BytesToStr(hash)).ToList(); |
|
46 |
var hashes = JsonConvert.SerializeObject(stringHashes); |
|
47 |
Assert.IsNotNull(topHash); |
|
48 |
} |
|
49 |
|
|
50 |
[Test] |
|
51 |
public void TestHashmapStorage() |
|
52 |
{ |
|
53 |
var file = "e:\\pithos\\vlc-1.1.11-win32.exe"; |
|
54 |
|
|
55 |
decimal blockSize = 4*1024*1024; |
|
56 |
|
|
57 |
var fileSize = new FileInfo(file).Length; |
|
58 |
var numBlocks = decimal.Ceiling(fileSize/blockSize); |
|
59 |
|
|
60 |
var md5 = Signature.CalculateMD5(file); |
|
61 |
|
|
62 |
var hash1 = Signature.CalculateTreeHashAsync(file, (int) blockSize, "sha256").Result; |
|
63 |
hash1.FileId = Guid.NewGuid(); |
|
64 |
var task = hash1.Save(@"e:\") |
|
65 |
.ContinueWith(_ => TreeHash.LoadTreeHash(@"e:\", hash1.FileId)).Unwrap(); |
|
66 |
task.ContinueWith(t => |
|
67 |
{ |
|
68 |
var hash = t.Result; |
|
69 |
Assert.AreEqual(hash1.Hashes, hash.Hashes.ToArray()); |
|
70 |
int i = 0; |
|
71 |
}).Wait(); |
|
72 |
} |
|
73 |
|
|
74 |
public static string BytesToStr(byte[] bytes) |
|
75 |
{ |
|
76 |
var str = new StringBuilder(); |
|
77 |
|
|
78 |
foreach (byte t in bytes) |
|
79 |
str.AppendFormat("{0:X2}", t); |
|
80 |
|
|
81 |
return str.ToString(); |
|
82 |
} |
|
83 |
|
|
84 |
|
|
85 |
[Test] |
|
86 |
public void TestTopHashEmpty() |
|
87 |
{ |
|
88 |
using (var hasher = HashAlgorithm.Create("sha256")) |
|
89 |
{ |
|
90 |
var hashEmpty = hasher.ComputeHash(new byte[] {}); |
|
91 |
|
|
92 |
var empty = new List<byte[]>(); |
|
93 |
var hash = Signature.CalculateTopHash(empty,"sha256"); |
|
94 |
Assert.IsNull(hash); |
|
95 |
} |
|
96 |
} |
|
97 |
} |
|
98 |
|
|
99 |
} |
b/trunk/Pithos.Core/Agents/CloudAction.cs | ||
---|---|---|
1 |
using System; |
|
2 |
using System.IO; |
|
3 |
using System.Threading; |
|
4 |
using Pithos.Interfaces; |
|
5 |
using Pithos.Network; |
|
6 |
|
|
7 |
namespace Pithos.Core.Agents |
|
8 |
{ |
|
9 |
public enum CloudActionType |
|
10 |
{ |
|
11 |
MustSynch, |
|
12 |
UploadUnconditional, |
|
13 |
DownloadUnconditional, |
|
14 |
DeleteLocal, |
|
15 |
DeleteCloud, |
|
16 |
RenameCloud |
|
17 |
} |
|
18 |
|
|
19 |
public class CloudAction |
|
20 |
{ |
|
21 |
public CloudActionType Action { get; set; } |
|
22 |
public FileInfo LocalFile { get; set; } |
|
23 |
public ObjectInfo CloudFile { get; set; } |
|
24 |
|
|
25 |
public Lazy<string> LocalHash { get; private set; } |
|
26 |
|
|
27 |
public string OldFileName { get; set; } |
|
28 |
public string OldPath { get; set; } |
|
29 |
public string NewFileName { get; set; } |
|
30 |
public string NewPath { get; set; } |
|
31 |
|
|
32 |
public CloudAction(CloudActionType action, string oldPath, string oldFileName, string newFileName, string newPath) |
|
33 |
{ |
|
34 |
Action = action; |
|
35 |
OldFileName = oldFileName; |
|
36 |
OldPath = oldPath; |
|
37 |
NewFileName = newFileName; |
|
38 |
NewPath = newPath; |
|
39 |
LocalHash = new Lazy<string>(() => Signature.CalculateMD5(NewFileName), LazyThreadSafetyMode.ExecutionAndPublication); |
|
40 |
} |
|
41 |
|
|
42 |
public CloudAction(CloudActionType action, FileInfo localFile, ObjectInfo cloudFile) |
|
43 |
{ |
|
44 |
Action = action; |
|
45 |
LocalFile = localFile; |
|
46 |
CloudFile = cloudFile; |
|
47 |
//Skip Hash calculation for folders |
|
48 |
if (LocalFile != null) |
|
49 |
LocalHash = new Lazy<string>(() => Signature.CalculateMD5(LocalFile.FullName), LazyThreadSafetyMode.ExecutionAndPublication); |
|
50 |
} |
|
51 |
|
|
52 |
} |
|
53 |
} |
b/trunk/Pithos.Core/Agents/FileAgent.cs | ||
---|---|---|
1 |
using System; |
|
2 |
using System.Collections.Generic; |
|
3 |
using System.ComponentModel.Composition; |
|
4 |
using System.Diagnostics; |
|
5 |
using System.IO; |
|
6 |
using System.Linq; |
|
7 |
using System.Text; |
|
8 |
using Pithos.Interfaces; |
|
9 |
using Pithos.Network; |
|
10 |
|
|
11 |
namespace Pithos.Core.Agents |
|
12 |
{ |
|
13 |
[Export] |
|
14 |
public class FileAgent |
|
15 |
{ |
|
16 |
Agent<WorkflowState> _agent; |
|
17 |
private FileSystemWatcher _watcher; |
|
18 |
|
|
19 |
[Import] |
|
20 |
public IStatusKeeper StatusKeeper { get; set; } |
|
21 |
[Import] |
|
22 |
public IPithosWorkflow Workflow { get; set; } |
|
23 |
[Import] |
|
24 |
public WorkflowAgent WorkflowAgent { get; set; } |
|
25 |
|
|
26 |
public string RootPath { get; private set; } |
|
27 |
|
|
28 |
public void Start(string rootPath) |
|
29 |
{ |
|
30 |
RootPath = rootPath; |
|
31 |
_watcher = new FileSystemWatcher(rootPath); |
|
32 |
_watcher.Changed += OnFileEvent; |
|
33 |
_watcher.Created += OnFileEvent; |
|
34 |
_watcher.Deleted += OnFileEvent; |
|
35 |
_watcher.Renamed += OnRenameEvent; |
|
36 |
_watcher.EnableRaisingEvents = true; |
|
37 |
|
|
38 |
|
|
39 |
_agent = Agent<WorkflowState>.Start(inbox => |
|
40 |
{ |
|
41 |
Action loop = null; |
|
42 |
loop = () => |
|
43 |
{ |
|
44 |
var message = inbox.Receive(); |
|
45 |
var process = message.ContinueWith(t => |
|
46 |
{ |
|
47 |
var state = t.Result; |
|
48 |
Process(state); |
|
49 |
inbox.DoAsync(loop); |
|
50 |
}); |
|
51 |
|
|
52 |
process.ContinueWith(t => |
|
53 |
{ |
|
54 |
inbox.DoAsync(loop); |
|
55 |
if (t.IsFaulted) |
|
56 |
{ |
|
57 |
var ex = t.Exception.InnerException; |
|
58 |
if (ex is OperationCanceledException) |
|
59 |
inbox.Stop(); |
|
60 |
Trace.TraceError("[ERROR] File Event Processing:\r{0}", ex); |
|
61 |
} |
|
62 |
}); |
|
63 |
|
|
64 |
}; |
|
65 |
loop(); |
|
66 |
}); |
|
67 |
} |
|
68 |
|
|
69 |
public bool Pause |
|
70 |
{ |
|
71 |
get { return _watcher == null || !_watcher.EnableRaisingEvents; } |
|
72 |
set |
|
73 |
{ |
|
74 |
if (_watcher != null) |
|
75 |
_watcher.EnableRaisingEvents = !value; |
|
76 |
} |
|
77 |
} |
|
78 |
|
|
79 |
public string FragmentsPath { get; set; } |
|
80 |
|
|
81 |
public void Post(WorkflowState workflowState) |
|
82 |
{ |
|
83 |
_agent.Post(workflowState); |
|
84 |
} |
|
85 |
|
|
86 |
public void Stop() |
|
87 |
{ |
|
88 |
if (_watcher != null) |
|
89 |
{ |
|
90 |
_watcher.Changed -= OnFileEvent; |
|
91 |
_watcher.Created -= OnFileEvent; |
|
92 |
_watcher.Deleted -= OnFileEvent; |
|
93 |
_watcher.Renamed -= OnRenameEvent; |
|
94 |
_watcher.Dispose(); |
|
95 |
} |
|
96 |
_watcher = null; |
|
97 |
|
|
98 |
_agent.Stop(); |
|
99 |
} |
|
100 |
|
|
101 |
// Enumerate all files in the Pithos directory except those in the Fragment folder |
|
102 |
// and files with a .ignore extension |
|
103 |
public IEnumerable<string> EnumerateFiles(string searchPattern="*") |
|
104 |
{ |
|
105 |
var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories) |
|
106 |
where !Ignore(filePath) |
|
107 |
select filePath; |
|
108 |
return monitoredFiles; |
|
109 |
} |
|
110 |
|
|
111 |
public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*") |
|
112 |
{ |
|
113 |
var rootDir = new DirectoryInfo(RootPath); |
|
114 |
var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories) |
|
115 |
where !Ignore(file.FullName) |
|
116 |
select file; |
|
117 |
return monitoredFiles; |
|
118 |
} |
|
119 |
|
|
120 |
public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*") |
|
121 |
{ |
|
122 |
var rootDir = new DirectoryInfo(RootPath); |
|
123 |
var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories) |
|
124 |
where !Ignore(file.FullName) |
|
125 |
select file.AsRelativeUrlTo(RootPath); |
|
126 |
return monitoredFiles; |
|
127 |
} |
|
128 |
|
|
129 |
|
|
130 |
|
|
131 |
|
|
132 |
private bool Ignore(string filePath) |
|
133 |
{ |
|
134 |
if (filePath.StartsWith(FragmentsPath)) |
|
135 |
return true; |
|
136 |
return false; |
|
137 |
} |
|
138 |
|
|
139 |
//Post a Change message for all events except rename |
|
140 |
void OnFileEvent(object sender, FileSystemEventArgs e) |
|
141 |
{ |
|
142 |
//Ignore events that affect the Fragments folder |
|
143 |
var filePath = e.FullPath; |
|
144 |
if (Ignore(filePath)) |
|
145 |
return; |
|
146 |
_agent.Post(new WorkflowState { Path = filePath, FileName = e.Name, TriggeringChange = e.ChangeType }); |
|
147 |
} |
|
148 |
|
|
149 |
|
|
150 |
//Post a Change message for renames containing the old and new names |
|
151 |
void OnRenameEvent(object sender, RenamedEventArgs e) |
|
152 |
{ |
|
153 |
var oldFullPath = e.OldFullPath; |
|
154 |
var fullPath = e.FullPath; |
|
155 |
if (Ignore(oldFullPath) || Ignore(fullPath)) |
|
156 |
return; |
|
157 |
|
|
158 |
_agent.Post(new WorkflowState |
|
159 |
{ |
|
160 |
OldPath = oldFullPath, |
|
161 |
OldFileName = e.OldName, |
|
162 |
Path = fullPath, |
|
163 |
FileName = e.Name, |
|
164 |
TriggeringChange = e.ChangeType |
|
165 |
}); |
|
166 |
} |
|
167 |
|
|
168 |
|
|
169 |
private void Process(WorkflowState state) |
|
170 |
{ |
|
171 |
Debug.Assert(!Ignore(state.Path)); |
|
172 |
|
|
173 |
var networkState = NetworkGate.GetNetworkState(state.Path); |
|
174 |
//Skip if the file is already being downloaded or uploaded and |
|
175 |
//the change is create or modify |
|
176 |
if (networkState != NetworkOperation.None && |
|
177 |
( |
|
178 |
state.TriggeringChange == WatcherChangeTypes.Created || |
|
179 |
state.TriggeringChange == WatcherChangeTypes.Changed |
|
180 |
)) |
|
181 |
return; |
|
182 |
UpdateFileStatus(state); |
|
183 |
UpdateOverlayStatus(state); |
|
184 |
UpdateFileChecksum(state); |
|
185 |
WorkflowAgent.Post(state); |
|
186 |
} |
|
187 |
|
|
188 |
private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus> |
|
189 |
{ |
|
190 |
{WatcherChangeTypes.Created,FileStatus.Created}, |
|
191 |
{WatcherChangeTypes.Changed,FileStatus.Modified}, |
|
192 |
{WatcherChangeTypes.Deleted,FileStatus.Deleted}, |
|
193 |
{WatcherChangeTypes.Renamed,FileStatus.Renamed} |
|
194 |
}; |
|
195 |
|
|
196 |
private WorkflowState UpdateFileStatus(WorkflowState state) |
|
197 |
{ |
|
198 |
Debug.Assert(!state.Path.Contains("fragments")); |
|
199 |
Debug.Assert(!state.Path.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase)); |
|
200 |
|
|
201 |
string path = state.Path; |
|
202 |
FileStatus status = _statusDict[state.TriggeringChange]; |
|
203 |
var oldStatus = Workflow.StatusKeeper.GetFileStatus(path); |
|
204 |
if (status == oldStatus) |
|
205 |
{ |
|
206 |
state.Status = status; |
|
207 |
state.Skip = true; |
|
208 |
return state; |
|
209 |
} |
|
210 |
if (state.Status == FileStatus.Renamed) |
|
211 |
Workflow.ClearFileStatus(path); |
|
212 |
|
|
213 |
state.Status = Workflow.SetFileStatus(path, status); |
|
214 |
return state; |
|
215 |
} |
|
216 |
|
|
217 |
private WorkflowState UpdateOverlayStatus(WorkflowState state) |
|
218 |
{ |
|
219 |
if (state.Skip) |
|
220 |
return state; |
|
221 |
|
|
222 |
switch (state.Status) |
|
223 |
{ |
|
224 |
case FileStatus.Created: |
|
225 |
case FileStatus.Modified: |
|
226 |
this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified); |
|
227 |
break; |
|
228 |
case FileStatus.Deleted: |
|
229 |
this.StatusKeeper.RemoveFileOverlayStatus(state.Path); |
|
230 |
break; |
|
231 |
case FileStatus.Renamed: |
|
232 |
this.StatusKeeper.RemoveFileOverlayStatus(state.OldPath); |
|
233 |
this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified); |
|
234 |
break; |
|
235 |
case FileStatus.Unchanged: |
|
236 |
this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal); |
|
237 |
break; |
|
238 |
} |
|
239 |
|
|
240 |
if (state.Status == FileStatus.Deleted) |
|
241 |
NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path)); |
|
242 |
else |
|
243 |
NativeMethods.RaiseChangeNotification(state.Path); |
|
244 |
return state; |
|
245 |
} |
|
246 |
|
|
247 |
|
|
248 |
private WorkflowState UpdateFileChecksum(WorkflowState state) |
|
249 |
{ |
|
250 |
if (state.Skip) |
|
251 |
return state; |
|
252 |
|
|
253 |
if (state.Status == FileStatus.Deleted) |
|
254 |
return state; |
|
255 |
|
|
256 |
var path = state.Path; |
|
257 |
//Skip calculation for folders |
|
258 |
if (Directory.Exists(path)) |
|
259 |
return state; |
|
260 |
|
|
261 |
string hash = Signature.CalculateMD5(path); |
|
262 |
|
|
263 |
StatusKeeper.UpdateFileChecksum(path, hash); |
|
264 |
|
|
265 |
state.Hash = hash; |
|
266 |
return state; |
|
267 |
} |
|
268 |
|
|
269 |
|
|
270 |
|
|
271 |
|
|
272 |
} |
|
273 |
} |
b/trunk/Pithos.Core/Agents/FileInfoExtensions.cs | ||
---|---|---|
1 |
using System; |
|
2 |
using System.Collections.Generic; |
|
3 |
using System.Linq; |
|
4 |
using System.Text; |
|
5 |
using System.IO; |
|
6 |
using System.Text.RegularExpressions; |
|
7 |
|
|
8 |
namespace Pithos.Core.Agents |
|
9 |
{ |
|
10 |
static class FileInfoExtensions |
|
11 |
{ |
|
12 |
public static string AsRelativeTo(this FileInfo fileInfo,string path ) |
|
13 |
{ |
|
14 |
if (!path.EndsWith("\\")) |
|
15 |
path=path.ToLower() + "\\"; |
|
16 |
int pathLength = path.Length; |
|
17 |
|
|
18 |
var filePath = fileInfo.FullName; |
|
19 |
|
|
20 |
if (!filePath.StartsWith(path,StringComparison.InvariantCultureIgnoreCase)) |
|
21 |
throw new ArgumentException(String.Format("The path {0} doesn't contain the file {1}",path,filePath)); |
|
22 |
|
|
23 |
var relativePath = filePath.Substring(pathLength, filePath.Length - pathLength); |
|
24 |
|
|
25 |
return relativePath; |
|
26 |
} |
|
27 |
|
|
28 |
public static string AsRelativeUrlTo(this FileInfo fileInfo,string path ) |
|
29 |
{ |
|
30 |
|
|
31 |
var relativePath = fileInfo.AsRelativeTo(path); |
|
32 |
var replacedSlashes = relativePath.Replace("\\","/"); |
|
33 |
var escaped = Uri.EscapeUriString(replacedSlashes); |
|
34 |
return escaped; |
|
35 |
} |
|
36 |
|
|
37 |
public static string RelativeUriToFilePath(this Uri uri) |
|
38 |
{ |
|
39 |
return RelativeUrlToFilePath(uri.ToString()); |
|
40 |
} |
|
41 |
|
|
42 |
public static string RelativeUrlToFilePath(this string url) |
|
43 |
{ |
|
44 |
var unescaped=Uri.UnescapeDataString(url); |
|
45 |
var path = unescaped.Replace("/", "\\"); |
|
46 |
return path; |
|
47 |
} |
|
48 |
|
|
49 |
|
|
50 |
} |
|
51 |
} |
/dev/null | ||
---|---|---|
1 |
using System; |
|
2 |
using System.Collections.Generic; |
|
3 |
using System.ComponentModel.Composition; |
|
4 |
using System.Diagnostics; |
|
5 |
using System.IO; |
|
6 |
using System.Linq; |
|
7 |
using System.Text; |
|
8 |
using Pithos.Interfaces; |
|
9 |
|
|
10 |
namespace Pithos.Core.Agents |
|
11 |
{ |
|
12 |
[Export] |
|
13 |
public class FileWatcherAgent |
|
14 |
{ |
|
15 |
Agent<WorkflowState> _agent; |
|
16 |
private FileSystemWatcher _watcher; |
|
17 |
|
|
18 |
[Import] |
|
19 |
public IStatusKeeper StatusKeeper { get; set; } |
|
20 |
[Import] |
|
21 |
public IPithosWorkflow Workflow { get; set; } |
|
22 |
[Import] |
|
23 |
public WorkflowAgent WorkflowAgent { get; set; } |
|
24 |
|
|
25 |
public void Start(string path) |
|
26 |
{ |
|
27 |
_watcher = new FileSystemWatcher(path); |
|
28 |
_watcher.Changed += OnFileEvent; |
|
29 |
_watcher.Created += OnFileEvent; |
|
30 |
_watcher.Deleted += OnFileEvent; |
|
31 |
_watcher.Renamed += OnRenameEvent; |
|
32 |
_watcher.EnableRaisingEvents = true; |
|
33 |
|
|
34 |
|
|
35 |
_agent = Agent<WorkflowState>.Start(inbox => |
|
36 |
{ |
|
37 |
Action loop = null; |
|
38 |
loop = () => |
|
39 |
{ |
|
40 |
var message = inbox.Receive(); |
|
41 |
var process = message.ContinueWith(t => |
|
42 |
{ |
|
43 |
var state = t.Result; |
|
44 |
Process(state); |
|
45 |
inbox.DoAsync(loop); |
|
46 |
}); |
|
47 |
|
|
48 |
process.ContinueWith(t => |
|
49 |
{ |
|
50 |
inbox.DoAsync(loop); |
|
51 |
if (t.IsFaulted) |
|
52 |
{ |
|
53 |
var ex = t.Exception.InnerException; |
|
54 |
if (ex is OperationCanceledException) |
|
55 |
inbox.Stop(); |
|
56 |
Trace.TraceError("[ERROR] File Event Processing:\r{0}", ex); |
|
57 |
} |
|
58 |
}); |
|
59 |
|
|
60 |
}; |
|
61 |
loop(); |
|
62 |
}); |
|
63 |
} |
|
64 |
|
|
65 |
public bool Pause |
|
66 |
{ |
|
67 |
get { return _watcher == null || !_watcher.EnableRaisingEvents; } |
|
68 |
set |
|
69 |
{ |
|
70 |
if (_watcher != null) |
|
71 |
_watcher.EnableRaisingEvents = !value; |
|
72 |
} |
|
73 |
} |
|
74 |
|
|
75 |
public void Post(WorkflowState workflowState) |
|
76 |
{ |
|
77 |
_agent.Post(workflowState); |
|
78 |
} |
|
79 |
|
|
80 |
public void Stop() |
|
81 |
{ |
|
82 |
if (_watcher != null) |
|
83 |
{ |
|
84 |
_watcher.Changed -= OnFileEvent; |
|
85 |
_watcher.Created -= OnFileEvent; |
|
86 |
_watcher.Deleted -= OnFileEvent; |
|
87 |
_watcher.Renamed -= OnRenameEvent; |
|
88 |
_watcher.Dispose(); |
|
89 |
} |
|
90 |
_watcher = null; |
|
91 |
|
|
92 |
_agent.Stop(); |
|
93 |
} |
|
94 |
|
|
95 |
void OnFileEvent(object sender, FileSystemEventArgs e) |
|
96 |
{ |
|
97 |
_agent.Post(new WorkflowState { Path = e.FullPath, FileName = e.Name, TriggeringChange = e.ChangeType }); |
|
98 |
} |
|
99 |
|
|
100 |
void OnRenameEvent(object sender, RenamedEventArgs e) |
|
101 |
{ |
|
102 |
_agent.Post(new WorkflowState |
|
103 |
{ |
|
104 |
OldPath = e.OldFullPath, |
|
105 |
OldFileName = e.OldName, |
|
106 |
Path = e.FullPath, |
|
107 |
FileName = e.Name, |
|
108 |
TriggeringChange = e.ChangeType |
|
109 |
}); |
|
110 |
} |
|
111 |
|
|
112 |
|
|
113 |
private void Process(WorkflowState state) |
|
114 |
{ |
|
115 |
var networkState = StatusKeeper.GetNetworkState(state.Path); |
|
116 |
//Skip if the file is already being downloaded or uploaded and |
|
117 |
//the change is create or modify |
|
118 |
if (networkState != NetworkState.None && |
|
119 |
( |
|
120 |
state.TriggeringChange == WatcherChangeTypes.Created || |
|
121 |
state.TriggeringChange == WatcherChangeTypes.Changed |
|
122 |
)) |
|
123 |
return; |
|
124 |
UpdateFileStatus(state); |
|
125 |
UpdateOverlayStatus(state); |
|
126 |
UpdateFileChecksum(state); |
|
127 |
WorkflowAgent.Post(state); |
|
128 |
} |
|
129 |
|
|
130 |
private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus> |
|
131 |
{ |
|
132 |
{WatcherChangeTypes.Created,FileStatus.Created}, |
|
133 |
{WatcherChangeTypes.Changed,FileStatus.Modified}, |
|
134 |
{WatcherChangeTypes.Deleted,FileStatus.Deleted}, |
|
135 |
{WatcherChangeTypes.Renamed,FileStatus.Renamed} |
|
136 |
}; |
|
137 |
|
|
138 |
private WorkflowState UpdateFileStatus(WorkflowState state) |
|
139 |
{ |
|
140 |
string path = state.Path; |
|
141 |
FileStatus status = _statusDict[state.TriggeringChange]; |
|
142 |
var oldStatus = Workflow.StatusKeeper.GetFileStatus(path); |
|
143 |
if (status == oldStatus) |
|
144 |
{ |
|
145 |
state.Status = status; |
|
146 |
state.Skip = true; |
|
147 |
return state; |
|
148 |
} |
|
149 |
if (state.Status == FileStatus.Renamed) |
|
150 |
Workflow.ClearFileStatus(path); |
|
151 |
|
|
152 |
state.Status = Workflow.SetFileStatus(path, status); |
|
153 |
return state; |
|
154 |
} |
|
155 |
|
|
156 |
private WorkflowState UpdateOverlayStatus(WorkflowState state) |
|
157 |
{ |
|
158 |
if (state.Skip) |
|
159 |
return state; |
|
160 |
|
|
161 |
switch (state.Status) |
|
162 |
{ |
|
163 |
case FileStatus.Created: |
|
164 |
case FileStatus.Modified: |
|
165 |
this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified); |
|
166 |
break; |
|
167 |
case FileStatus.Deleted: |
|
168 |
this.StatusKeeper.RemoveFileOverlayStatus(state.Path); |
|
169 |
break; |
|
170 |
case FileStatus.Renamed: |
|
171 |
this.StatusKeeper.RemoveFileOverlayStatus(state.OldPath); |
|
172 |
this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified); |
|
173 |
break; |
|
174 |
case FileStatus.Unchanged: |
|
175 |
this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal); |
|
176 |
break; |
|
177 |
} |
|
178 |
|
|
179 |
if (state.Status == FileStatus.Deleted) |
|
180 |
Workflow.RaiseChangeNotification(Path.GetDirectoryName(state.Path)); |
|
181 |
else |
|
182 |
Workflow.RaiseChangeNotification(state.Path); |
|
183 |
return state; |
|
184 |
} |
|
185 |
|
|
186 |
|
|
187 |
private WorkflowState UpdateFileChecksum(WorkflowState state) |
|
188 |
{ |
|
189 |
if (state.Skip) |
|
190 |
return state; |
|
191 |
|
|
192 |
if (state.Status == FileStatus.Deleted) |
|
193 |
return state; |
|
194 |
|
|
195 |
string path = state.Path; |
|
196 |
string hash = Signature.CalculateMD5(path); |
|
197 |
|
|
198 |
StatusKeeper.UpdateFileChecksum(path, hash); |
|
199 |
|
|
200 |
state.Hash = hash; |
|
201 |
return state; |
|
202 |
} |
|
203 |
|
|
204 |
|
|
205 |
|
|
206 |
|
|
207 |
} |
|
208 |
} |
b/trunk/Pithos.Core/NetworkGate.cs | ||
---|---|---|
1 |
using System; |
|
2 |
using System.Collections.Concurrent; |
|
3 |
using System.Collections.Generic; |
|
4 |
using System.Diagnostics.Contracts; |
|
5 |
using System.IO; |
|
6 |
|
|
7 |
namespace Pithos.Core |
|
8 |
{ |
|
9 |
public enum NetworkOperation |
|
10 |
{ |
|
11 |
None, |
|
12 |
Uploading, |
|
13 |
Downloading |
|
14 |
} |
|
15 |
|
|
16 |
//The NetworkGate prevents starting download/uploads for files that are already in the process of downloading, |
|
17 |
//uploading. |
|
18 |
public class NetworkGate:IDisposable |
|
19 |
{ |
|
20 |
//The state of each file is stored in a thread-safe dictionary |
|
21 |
static readonly ConcurrentDictionary<string, NetworkOperation> NetworkState = new ConcurrentDictionary<string, NetworkOperation>(); |
|
22 |
|
|
23 |
public static NetworkOperation GetNetworkState(string path) |
|
24 |
{ |
|
25 |
if (String.IsNullOrWhiteSpace(path)) |
|
26 |
throw new ArgumentNullException("path"); |
|
27 |
if (!Path.IsPathRooted(path)) |
|
28 |
throw new ArgumentException("path must be a rooted path", "path"); |
|
29 |
Contract.EndContractBlock(); |
|
30 |
|
|
31 |
NetworkOperation operation; |
|
32 |
if (NetworkState.TryGetValue(path.ToLower(), out operation)) |
|
33 |
return operation; |
|
34 |
return NetworkOperation.None; |
|
35 |
} |
|
36 |
|
|
37 |
//Store a network operation for the specified path |
|
38 |
public static void SetNetworkState(string path, NetworkOperation operation) |
|
39 |
{ |
|
40 |
if (String.IsNullOrWhiteSpace(path)) |
|
41 |
throw new ArgumentNullException("path"); |
|
42 |
if (!Path.IsPathRooted(path)) |
|
43 |
throw new ArgumentException("path must be a rooted path", "path"); |
|
44 |
Contract.EndContractBlock(); |
|
45 |
|
|
46 |
var lower = path.ToLower(); |
|
47 |
NetworkState[lower] = operation; |
|
48 |
//By default, None values don't need to be stored. They are stored anyway |
|
49 |
//because TryRemove may fail. |
|
50 |
if (operation == NetworkOperation.None) |
|
51 |
{ |
|
52 |
NetworkOperation oldOperation; |
|
53 |
NetworkState.TryRemove(lower, out oldOperation); |
|
54 |
} |
|
55 |
} |
|
56 |
|
|
57 |
//Clients should acquire a NetworkGate before starting any network operation. |
|
58 |
//If Acquire fails, another network operation is already in progress |
|
59 |
public static NetworkGate Acquire(string path, NetworkOperation operation) |
|
60 |
{ |
|
61 |
if (String.IsNullOrWhiteSpace(path)) |
|
62 |
throw new ArgumentNullException("path"); |
|
63 |
if (!Path.IsPathRooted(path)) |
|
64 |
throw new ArgumentException("path must be a rooted path", "path"); |
|
65 |
Contract.EndContractBlock(); |
|
66 |
|
|
67 |
var state = GetNetworkState(path); |
|
68 |
//If no operation is in progress, return a NetworkGate |
|
69 |
return (state == NetworkOperation.None) |
|
70 |
? new NetworkGate(path, operation) |
|
71 |
//otherwise return a gate with Fail flagged |
|
72 |
: new NetworkGate(path, NetworkOperation.None); |
|
73 |
} |
|
74 |
|
|
75 |
|
|
76 |
|
|
77 |
public string FilePath { get; private set; } |
|
78 |
public NetworkOperation Operation { get; private set; } |
|
79 |
|
|
80 |
private NetworkGate(string path,NetworkOperation operation) |
|
81 |
{ |
|
82 |
if (String.IsNullOrWhiteSpace(path)) |
|
83 |
throw new ArgumentNullException("path"); |
|
84 |
Contract.EndContractBlock(); |
|
85 |
|
|
86 |
Operation = operation; |
|
87 |
FilePath = path.ToLower(); |
|
88 |
|
|
89 |
//Skip dummy operations (those with Operation == None) |
|
90 |
if (Operation != NetworkOperation.None) |
|
91 |
//and store the file's operation |
|
92 |
SetNetworkState(FilePath, operation); |
|
93 |
} |
|
94 |
|
|
95 |
//A NetworkGate has Failed if its operation is None |
|
96 |
public bool Failed { get { return Operation == NetworkOperation.None; } } |
|
97 |
|
|
98 |
//Release a gate by setting the NetworkOperation to None |
|
99 |
public void Release() |
|
100 |
{ |
|
101 |
//Skip Failed flags |
|
102 |
if (!Failed) |
|
103 |
//And reset the operation state for the file |
|
104 |
SetNetworkState(FilePath,NetworkOperation.None); |
|
105 |
} |
|
106 |
|
|
107 |
|
|
108 |
public void Dispose() |
|
109 |
{ |
|
110 |
Dispose(true); |
|
111 |
GC.SuppressFinalize(this); |
|
112 |
} |
|
113 |
|
|
114 |
protected void Dispose(bool disposing) |
|
115 |
{ |
|
116 |
if (disposing) |
|
117 |
{ |
|
118 |
Release(); |
|
119 |
} |
|
120 |
} |
|
121 |
|
|
122 |
} |
|
123 |
} |
/dev/null | ||
---|---|---|
1 |
using System; |
|
2 |
using System.Collections.Concurrent; |
|
3 |
using System.Collections.Generic; |
|
4 |
using System.Diagnostics.Contracts; |
|
5 |
using System.IO; |
|
6 |
using System.Runtime.Remoting.Metadata.W3cXsd2001; |
|
7 |
using System.Security.Cryptography; |
|
8 |
using System.Text; |
|
9 |
using System.Threading.Tasks; |
|
10 |
using System.Linq; |
|
11 |
|
|
12 |
namespace Pithos.Core |
|
13 |
{ |
|
14 |
public static class Signature |
|
15 |
{ |
|
16 |
public static string CalculateMD5(string path) |
|
17 |
{ |
|
18 |
if (String.IsNullOrWhiteSpace(path)) |
|
19 |
throw new ArgumentNullException("path"); |
|
20 |
Contract.EndContractBlock(); |
|
21 |
|
|
22 |
string hash; |
|
23 |
using (var hasher = MD5.Create()) |
|
24 |
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, true)) |
|
25 |
{ |
|
26 |
var hashBytes = hasher.ComputeHash(stream); |
|
27 |
hash = hashBytes.ToHashString(); |
|
28 |
} |
|
29 |
return hash; |
|
30 |
} |
|
31 |
|
|
32 |
/* |
|
33 |
public static string BytesToString(byte[] hashBytes) |
|
34 |
{ |
|
35 |
var shb=new SoapHexBinary(hashBytes); |
|
36 |
return shb.ToString(); |
|
37 |
|
|
38 |
} |
|
39 |
|
|
40 |
|
|
41 |
public static byte[] StringToBytes(string hash) |
|
42 |
{ |
|
43 |
var shb=SoapHexBinary.Parse(hash); |
|
44 |
return shb.Value; |
|
45 |
} |
|
46 |
*/ |
|
47 |
|
|
48 |
public static byte[] ToBytes(this string hash) |
|
49 |
{ |
|
50 |
var shb = SoapHexBinary.Parse(hash); |
|
51 |
return shb.Value; |
|
52 |
} |
|
53 |
|
|
54 |
public static string ToHashString(this byte[] hashBytes) |
|
55 |
{ |
|
56 |
var shb = new SoapHexBinary(hashBytes); |
|
57 |
return shb.ToString(); |
|
58 |
} |
|
59 |
|
|
60 |
|
|
61 |
/// <summary> |
|
62 |
/// Calculates a file's tree hash synchronously, using the specified block size |
|
63 |
/// </summary> |
|
64 |
/// <param name="filePath">Path to an existing file</param> |
|
65 |
/// <param name="blockSize">Block size used to calculate leaf hashes</param> |
|
66 |
/// <param name="algorithm"></param> |
|
67 |
/// <returns>A <see cref="TreeHash"/> with the block hashes and top hash</returns> |
|
68 |
public static TreeHash CalculateTreeHash(string filePath, int blockSize, string algorithm) |
|
69 |
{ |
|
70 |
if (String.IsNullOrWhiteSpace(filePath)) |
|
71 |
throw new ArgumentNullException("filePath"); |
|
72 |
if (blockSize<=0) |
|
73 |
throw new ArgumentOutOfRangeException("blockSize","blockSize must be a value greater than zero "); |
|
74 |
if (String.IsNullOrWhiteSpace(algorithm)) |
|
75 |
throw new ArgumentNullException("algorithm"); |
|
76 |
Contract.EndContractBlock(); |
|
77 |
|
|
78 |
var list = new List<byte[]>(); |
|
79 |
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, false)) |
|
80 |
using (var hasher = HashAlgorithm.Create(algorithm)) |
|
81 |
{ |
|
82 |
int read; |
|
83 |
var buffer = new byte[blockSize]; |
|
84 |
while ((read = stream.Read(buffer, 0, blockSize)) > 0) |
|
85 |
{ |
|
86 |
var hash = hasher.ComputeHash(buffer, 0, read); |
|
87 |
list.Add(hash); |
|
88 |
} |
|
89 |
return new TreeHash(algorithm) { Hashes = list, |
|
90 |
BlockSize = blockSize, |
|
91 |
Bytes = stream.Length}; |
|
92 |
} |
|
93 |
} |
|
94 |
|
|
95 |
|
|
96 |
public static Task<TreeHash> CalculateTreeHashAsync(string path, int blockSize,string algorithm) |
|
97 |
{ |
|
98 |
if (String.IsNullOrWhiteSpace(path)) |
|
99 |
throw new ArgumentNullException("path"); |
|
100 |
if (blockSize <= 0) |
|
101 |
throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero "); |
|
102 |
if (String.IsNullOrWhiteSpace(algorithm)) |
|
103 |
throw new ArgumentNullException("algorithm"); |
|
104 |
Contract.EndContractBlock(); |
|
105 |
|
|
106 |
var hashes = new ConcurrentDictionary<int, byte[]>(); |
|
107 |
var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, true); |
|
108 |
|
|
109 |
return CalculateBlockHashesAsync(stream, blockSize, algorithm,hashes) |
|
110 |
.ContinueWith(t => { |
|
111 |
var length = stream.Length; |
|
112 |
stream.Close(); |
|
113 |
var list= t.Result.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList(); |
|
114 |
return new TreeHash(algorithm) { Hashes = list, |
|
115 |
BlockSize = blockSize, |
|
116 |
Bytes = length }; |
|
117 |
}); |
|
118 |
} |
|
119 |
|
|
120 |
public static byte[] CalculateTopHash(IEnumerable<byte[]> hashMap, string algorithm) |
|
121 |
{ |
|
122 |
if (hashMap == null) |
|
123 |
throw new ArgumentNullException("hashMap"); |
|
124 |
if (String.IsNullOrWhiteSpace(algorithm)) |
|
125 |
throw new ArgumentNullException("algorithm"); |
|
126 |
Contract.EndContractBlock(); |
|
127 |
|
|
128 |
using (var hasher = HashAlgorithm.Create(algorithm)) |
|
129 |
{ |
|
130 |
var i = 0; |
|
131 |
var count = hashMap.Count(); |
|
132 |
foreach (var block in hashMap) |
|
133 |
{ |
|
134 |
if (i++ != count - 1) |
|
135 |
hasher.TransformBlock(block, 0, block.Length, null, 0); |
|
136 |
else |
|
137 |
hasher.TransformFinalBlock(block, 0, block.Length); |
|
138 |
} |
|
139 |
|
|
140 |
var finalHash = hasher.Hash; |
|
141 |
|
|
142 |
return finalHash; |
|
143 |
} |
|
144 |
} |
|
145 |
|
|
146 |
private static Task<ConcurrentDictionary<int, byte[]>> CalculateBlockHashesAsync(FileStream stream, int blockSize, string algorithm, ConcurrentDictionary<int, byte[]> hashes, int index = 0) |
|
147 |
{ |
|
148 |
if (stream==null) |
|
149 |
throw new ArgumentNullException("stream"); |
|
150 |
if (hashes==null) |
|
151 |
throw new ArgumentNullException("hashes"); |
|
152 |
if (String.IsNullOrWhiteSpace(algorithm)) |
|
153 |
throw new ArgumentNullException("algorithm"); |
|
154 |
if (blockSize <= 0) |
|
155 |
throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero "); |
|
156 |
if (index< 0) |
|
157 |
throw new ArgumentOutOfRangeException("index", "index must be a non-negative value"); |
|
158 |
Contract.EndContractBlock(); |
|
159 |
|
|
160 |
|
|
161 |
var buffer = new byte[blockSize]; |
|
162 |
return stream.ReadAsync(buffer, 0, blockSize).ContinueWith(t => |
|
163 |
{ |
|
164 |
var read = t.Result; |
|
165 |
|
|
166 |
var nextTask = read == blockSize |
|
167 |
? CalculateBlockHashesAsync(stream, blockSize, algorithm, hashes, index + 1) |
|
168 |
: Task.Factory.StartNew(() => hashes); |
|
169 |
|
|
170 |
using (var hasher = HashAlgorithm.Create(algorithm)) |
|
171 |
{ |
|
172 |
var hash = hasher.ComputeHash(buffer, 0, read); |
|
173 |
hashes[index]=hash; |
|
174 |
} |
|
175 |
return nextTask; |
|
176 |
}).Unwrap(); |
|
177 |
} |
|
178 |
|
|
179 |
|
|
180 |
|
|
181 |
public static byte[] CalculateHash(byte[] buffer,string algorithm) |
|
182 |
{ |
|
183 |
if (buffer == null) |
|
184 |
throw new ArgumentNullException("buffer"); |
|
185 |
if (String.IsNullOrWhiteSpace(algorithm)) |
|
186 |
throw new ArgumentNullException("algorithm"); |
|
187 |
Contract.EndContractBlock(); |
|
188 |
|
|
189 |
using (var hasher = HashAlgorithm.Create(algorithm)) |
|
190 |
{ |
|
191 |
var hash = hasher.ComputeHash(buffer, 0, buffer.Length); |
|
192 |
return hash; |
|
193 |
} |
|
194 |
} |
|
195 |
} |
|
196 |
} |
|
197 |
|
|
198 |
|
|
199 |
|
/dev/null | ||
---|---|---|
1 |
using System; |
|
2 |
using System.Collections.Generic; |
|
3 |
using System.Diagnostics.Contracts; |
|
4 |
using System.Linq; |
|
5 |
using System.Text; |
|
6 |
using System.Threading.Tasks; |
|
7 |
|
|
8 |
namespace Pithos.Interfaces |
|
9 |
{ |
|
10 |
[ContractClass(typeof(ICloudClientContract))] |
|
11 |
public interface ICloudClient |
|
12 |
{ |
|
13 |
string ApiKey { get; set; } |
|
14 |
string UserName { get; set; } |
|
15 |
Uri StorageUrl { get; set; } |
|
16 |
string Token { get; set; } |
|
17 |
bool UsePithos { get; set; } |
|
18 |
void Authenticate(string userName,string apiKey); |
|
19 |
Uri Proxy { get; set; } |
|
20 |
double DownloadPercentLimit { get; set; } |
|
21 |
double UploadPercentLimit { get; set; } |
|
22 |
string AuthenticationUrl { get; set; } |
|
23 |
|
|
24 |
|
|
25 |
IList<ContainerInfo> ListContainers(); |
|
26 |
IList<ObjectInfo> ListObjects(string container,DateTime? since=null); |
|
27 |
IList<ObjectInfo> ListObjects(string container, string folder, DateTime? since = null); |
|
28 |
bool ContainerExists(string container); |
|
29 |
ContainerInfo GetContainerInfo(string container); |
|
30 |
void CreateContainer(string container); |
|
31 |
void DeleteContainer(string container); |
|
32 |
|
|
33 |
Task GetObject(string container, string objectName, string fileName); |
|
34 |
Task PutObject(string container, string objectName, string fileName, string hash = null); |
|
35 |
void DeleteObject(string container, string objectName); |
|
36 |
void MoveObject(string sourceContainer, string oldObjectName, string targetContainer,string newObjectName); |
|
37 |
bool ObjectExists(string container,string objectName); |
|
38 |
ObjectInfo GetObjectInfo(string container, string objectName); |
|
39 |
void CreateFolder(string container, string folder); |
|
40 |
|
|
41 |
|
|
42 |
} |
|
43 |
|
|
44 |
|
|
45 |
[ContractClassFor(typeof(ICloudClient))] |
|
46 |
public abstract class ICloudClientContract:ICloudClient |
|
47 |
{ |
|
48 |
public string ApiKey { get; set; } |
|
49 |
public string UserName { get; set; } |
|
50 |
public Uri StorageUrl { get; set; } |
|
51 |
public string Token { get; set; } |
|
52 |
public Uri Proxy { get; set; } |
|
53 |
public double DownloadPercentLimit { get; set; } |
|
54 |
public double UploadPercentLimit { get; set; } |
|
55 |
|
|
56 |
public string AuthenticationUrl { get; set; } |
|
57 |
|
|
58 |
public bool UsePithos { get; set; } |
|
59 |
|
|
60 |
public void Authenticate(string userName, string apiKey) |
|
61 |
{ |
|
62 |
Contract.Requires<ArgumentNullException>(!String.IsNullOrWhiteSpace(apiKey), "ApiKey must be filled before calling Authenticate"); |
|
63 |
Contract.Requires<ArgumentNullException>(!String.IsNullOrWhiteSpace(userName), "UserName must be filled before calling Authenticate"); |
|
64 |
|
|
65 |
|
|
66 |
Contract.Ensures(apiKey==ApiKey); |
|
67 |
Contract.Ensures(userName==UserName); |
|
68 |
Contract.Ensures(StorageUrl!=null); |
|
69 |
Contract.Ensures(!String.IsNullOrWhiteSpace(Token)); |
|
70 |
|
|
71 |
} |
|
72 |
|
|
73 |
public IList<ContainerInfo> ListContainers() |
|
74 |
{ |
|
75 |
Contract.Requires(!String.IsNullOrWhiteSpace(Token)); |
|
76 |
Contract.Requires(StorageUrl!=null); |
|
77 |
|
|
78 |
return default(IList<ContainerInfo>); |
|
79 |
} |
|
80 |
|
|
81 |
public IList<ObjectInfo> ListObjects(string container, DateTime? since = null) |
|
82 |
{ |
|
83 |
Contract.Requires(!String.IsNullOrWhiteSpace(Token)); |
|
84 |
Contract.Requires(StorageUrl != null); |
|
85 |
Contract.Requires(!String.IsNullOrWhiteSpace(container)); |
|
86 |
|
|
87 |
return default(IList<ObjectInfo>); |
|
88 |
} |
|
89 |
|
|
90 |
public IList<ObjectInfo> ListObjects(string container, string folder, DateTime? since = null) |
|
91 |
{ |
|
92 |
Contract.Requires(!String.IsNullOrWhiteSpace(Token)); |
|
93 |
Contract.Requires(StorageUrl != null); |
|
94 |
Contract.Requires(!String.IsNullOrWhiteSpace(container)); |
|
95 |
Contract.Requires(!String.IsNullOrWhiteSpace(folder)); |
|
96 |
|
|
97 |
return default(IList<ObjectInfo>); |
|
98 |
} |
|
99 |
|
|
100 |
public bool ContainerExists(string container) |
|
101 |
{ |
|
102 |
Contract.Requires(!String.IsNullOrWhiteSpace(Token)); |
|
103 |
Contract.Requires(StorageUrl!=null); |
|
104 |
Contract.Requires(!String.IsNullOrWhiteSpace(container)); |
|
105 |
|
|
106 |
return default(bool); |
|
107 |
} |
|
108 |
|
|
109 |
public ContainerInfo GetContainerInfo(string container) |
|
110 |
{ |
|
111 |
Contract.Requires(!String.IsNullOrWhiteSpace(Token)); |
|
112 |
Contract.Requires(StorageUrl!=null); |
|
113 |
Contract.Requires(!String.IsNullOrWhiteSpace(container)); |
|
114 |
|
|
115 |
return default(ContainerInfo); |
|
116 |
} |
|
117 |
|
|
118 |
public void CreateContainer(string container) |
|
119 |
{ |
|
120 |
Contract.Requires(!String.IsNullOrWhiteSpace(Token)); |
|
121 |
Contract.Requires(StorageUrl!=null); |
|
122 |
Contract.Requires(!String.IsNullOrWhiteSpace(container)); |
|
123 |
} |
|
124 |
|
|
125 |
public void DeleteContainer(string container) |
|
126 |
{ |
|
127 |
Contract.Requires(!String.IsNullOrWhiteSpace(Token)); |
|
128 |
Contract.Requires(StorageUrl!=null); |
|
129 |
Contract.Requires(!String.IsNullOrWhiteSpace(container)); |
|
130 |
} |
|
131 |
|
|
132 |
public Task GetObject(string container, string objectName, string fileName) |
|
133 |
{ |
|
134 |
Contract.Requires(!String.IsNullOrWhiteSpace(Token)); |
|
135 |
Contract.Requires(StorageUrl!=null); |
|
136 |
Contract.Requires(!String.IsNullOrWhiteSpace(container)); |
|
137 |
Contract.Requires(!String.IsNullOrWhiteSpace(objectName)); |
|
138 |
|
|
139 |
return default(Task); |
|
140 |
} |
|
141 |
|
|
142 |
public Task PutObject(string container, string objectName, string fileName, string hash = null) |
|
143 |
{ |
|
144 |
Contract.Requires(!String.IsNullOrWhiteSpace(Token)); |
|
145 |
Contract.Requires(StorageUrl!=null); |
|
146 |
Contract.Requires(!String.IsNullOrWhiteSpace(container)); |
|
147 |
Contract.Requires(!String.IsNullOrWhiteSpace(fileName)); |
|
148 |
Contract.Requires(!String.IsNullOrWhiteSpace(objectName)); |
|
149 |
|
|
150 |
return default(Task); |
|
151 |
} |
|
152 |
|
|
153 |
public void DeleteObject(string container, string objectName) |
|
154 |
{ |
Also available in: Unified diff