Revision 5ce54458
b/trunk/Pithos.Client.WPF/Pithos.Client.WPF.csproj | ||
---|---|---|
117 | 117 |
<DependentUpon>FilePropertiesView.xaml</DependentUpon> |
118 | 118 |
</Compile> |
119 | 119 |
<Compile Include="FilePropertiesViewModel.cs" /> |
120 |
<Compile Include="NativeMethods.cs" /> |
|
120 | 121 |
<Compile Include="Notification.cs" /> |
121 | 122 |
<Compile Include="PithosAccount.cs" /> |
122 | 123 |
<Compile Include="PithosCommand.cs" /> |
b/trunk/Pithos.Client.WPF/PreferencesView.xaml | ||
---|---|---|
16 | 16 |
</ResourceDictionary.MergedDictionaries> |
17 | 17 |
</ResourceDictionary> |
18 | 18 |
</Window.Resources> |
19 |
<Window.TaskbarItemInfo> |
|
19 |
<!-- <Window.TaskbarItemInfo>
|
|
20 | 20 |
<TaskbarItemInfo Description="{Binding StatusMessage}" > |
21 | 21 |
<TaskbarItemInfo.ThumbButtonInfos> |
22 | 22 |
<ThumbButtonInfo> |
... | ... | |
24 | 24 |
</ThumbButtonInfo> |
25 | 25 |
</TaskbarItemInfo.ThumbButtonInfos> |
26 | 26 |
</TaskbarItemInfo> |
27 |
</Window.TaskbarItemInfo> |
|
27 |
</Window.TaskbarItemInfo>-->
|
|
28 | 28 |
<Grid> |
29 | 29 |
<Grid.RowDefinitions> |
30 | 30 |
<RowDefinition Height="*"/> |
b/trunk/Pithos.Client.WPF/PreferencesViewModel.cs | ||
---|---|---|
12 | 12 |
using System.Linq.Expressions; |
13 | 13 |
using System.Net; |
14 | 14 |
using System.Reflection; |
15 |
using System.Runtime.InteropServices; |
|
15 | 16 |
using System.Runtime.Serialization; |
16 | 17 |
using System.Windows; |
17 | 18 |
using System.Windows.Forms; |
... | ... | |
45 | 46 |
|
46 | 47 |
public PithosMonitor Monitor { get; private set; } |
47 | 48 |
|
48 |
public TaskbarViewModel Taskbar { get; set; } |
|
49 |
private TaskbarViewModel _taskbar; |
|
50 |
public TaskbarViewModel Taskbar |
|
51 |
{ |
|
52 |
get { return _taskbar; } |
|
53 |
set |
|
54 |
{ |
|
55 |
_taskbar = value; |
|
56 |
} |
|
57 |
} |
|
49 | 58 |
|
50 | 59 |
//ShellExtensionController _extensionController=new ShellExtensionController(); |
51 | 60 |
|
... | ... | |
257 | 266 |
|
258 | 267 |
public void RefreshOverlays() |
259 | 268 |
{ |
260 |
this.Monitor.Workflow.RaiseChangeNotification(Settings.PithosPath); |
|
269 |
string path=Settings.PithosPath; |
|
270 |
if (String.IsNullOrWhiteSpace(path)) |
|
271 |
throw new ArgumentNullException("path", "The path parameter must not be emtpy"); |
|
272 |
|
|
273 |
if (!Directory.Exists(path) && !File.Exists(path)) |
|
274 |
throw new FileNotFoundException("The specified file or path does not exist", path); |
|
275 |
|
|
276 |
|
|
277 |
IntPtr pathPointer = Marshal.StringToCoTaskMemAuto(path); |
|
278 |
|
|
279 |
try |
|
280 |
{ |
|
281 |
NativeMethods.SHChangeNotify(HChangeNotifyEventID.SHCNE_UPDATEITEM, |
|
282 |
HChangeNotifyFlags.SHCNF_PATHW | HChangeNotifyFlags.SHCNF_FLUSHNOWAIT, |
|
283 |
pathPointer, IntPtr.Zero); |
|
284 |
} |
|
285 |
finally |
|
286 |
{ |
|
287 |
Marshal.FreeHGlobal(pathPointer); |
|
288 |
} |
|
289 |
|
|
261 | 290 |
} |
262 | 291 |
#endregion |
263 | 292 |
|
b/trunk/Pithos.Client.WPF/Properties/Settings.Designer.cs | ||
---|---|---|
242 | 242 |
</ArrayOfAccountSettings>")] |
243 | 243 |
public global::Pithos.Interfaces.AccountsCollection Accounts { |
244 | 244 |
get { |
245 |
return ((global::Pithos.Interfaces.AccountsCollection)(this["Accounts"])); |
|
245 |
return ((global::Pithos.Interfaces.AccountsCollection)(this["Accounts"]));
|
|
246 | 246 |
} |
247 | 247 |
set { |
248 | 248 |
this["Accounts"] = value; |
b/trunk/Pithos.Client.WPF/TaskbarViewModel.cs | ||
---|---|---|
105 | 105 |
} |
106 | 106 |
|
107 | 107 |
|
108 |
private string _statusIcon; |
|
108 |
private string _statusIcon="Images/Tray.ico";
|
|
109 | 109 |
public string StatusIcon |
110 | 110 |
{ |
111 | 111 |
get { return _statusIcon; } |
b/trunk/Pithos.Core.Test/MockStatusKeeper.cs | ||
---|---|---|
6 | 6 |
using System.Text; |
7 | 7 |
using System.Threading; |
8 | 8 |
using Pithos.Interfaces; |
9 |
using Pithos.Network; |
|
9 | 10 |
|
10 | 11 |
namespace Pithos.Core.Test |
11 | 12 |
{ |
... | ... | |
95 | 96 |
throw new NotImplementedException(); |
96 | 97 |
} |
97 | 98 |
|
98 |
ConcurrentDictionary<string, NetworkState> _networkState = new ConcurrentDictionary<string, NetworkState>();
|
|
99 |
ConcurrentDictionary<string, NetworkOperation> _networkState = new ConcurrentDictionary<string, NetworkOperation>();
|
|
99 | 100 |
|
100 | 101 |
|
101 |
public void SetNetworkState(string path, NetworkState state)
|
|
102 |
public void SetNetworkState(string path, NetworkOperation operation)
|
|
102 | 103 |
{ |
103 |
_networkState[path.ToLower()] = state;
|
|
104 |
_networkState[path.ToLower()] = operation;
|
|
104 | 105 |
//Removing may fail so we store the "None" value anyway |
105 |
if (state == NetworkState.None)
|
|
106 |
if (operation == NetworkOperation.None)
|
|
106 | 107 |
{ |
107 |
NetworkState oldState;
|
|
108 |
_networkState.TryRemove(path, out oldState);
|
|
108 |
NetworkOperation oldOperation;
|
|
109 |
_networkState.TryRemove(path, out oldOperation);
|
|
109 | 110 |
} |
110 | 111 |
} |
111 | 112 |
|
112 |
public NetworkState GetNetworkState(string path)
|
|
113 |
public NetworkOperation GetNetworkState(string path)
|
|
113 | 114 |
{ |
114 |
NetworkState state;
|
|
115 |
if (_networkState.TryGetValue(path, out state))
|
|
116 |
return state;
|
|
117 |
return NetworkState.None;
|
|
115 |
NetworkOperation operation;
|
|
116 |
if (_networkState.TryGetValue(path, out operation))
|
|
117 |
return operation;
|
|
118 |
return NetworkOperation.None;
|
|
118 | 119 |
} |
119 | 120 |
|
120 | 121 |
public void StartProcessing(CancellationToken token) |
... | ... | |
124 | 125 |
|
125 | 126 |
public string BlockHash { get; set; } |
126 | 127 |
|
128 |
public int BlockSize { get; set; } |
|
129 |
|
|
127 | 130 |
|
128 | 131 |
private PithosStatus _pithosStatus = PithosStatus.InSynch; |
129 | 132 |
public void SetPithosStatus(PithosStatus status) |
b/trunk/Pithos.Core.Test/Pithos.Core.Test.csproj | ||
---|---|---|
92 | 92 |
<ItemGroup> |
93 | 93 |
<Compile Include="PithosWorkflowTest.cs" /> |
94 | 94 |
<Compile Include="Properties\AssemblyInfo.cs" /> |
95 |
<Compile Include="SignatureTest.cs" /> |
|
96 | 95 |
<Compile Include="StatusCheckerTest.cs" /> |
97 | 96 |
<Compile Include="MockSettings.cs" /> |
98 | 97 |
<Compile Include="MockStatusKeeper.cs" /> |
... | ... | |
111 | 110 |
<Project>{7EEFF32F-CCF8-436A-9E0B-F40434C09AF4}</Project> |
112 | 111 |
<Name>Pithos.Interfaces</Name> |
113 | 112 |
</ProjectReference> |
113 |
<ProjectReference Include="..\Pithos.Network\Pithos.Network.csproj"> |
|
114 |
<Project>{C8E2BC8B-C7F1-4222-855C-4B04A57FFDFD}</Project> |
|
115 |
<Name>Pithos.Network</Name> |
|
116 |
</ProjectReference> |
|
114 | 117 |
</ItemGroup> |
115 | 118 |
<ItemGroup> |
116 | 119 |
<None Include="packages.config" /> |
b/trunk/Pithos.Core.Test/PithosWorkflowTest.cs | ||
---|---|---|
33 | 33 |
|
34 | 34 |
Assert.DoesNotThrow(() => |
35 | 35 |
{ |
36 |
workflow.RaiseChangeNotification(path);
|
|
36 |
NativeMethods.RaiseChangeNotification(path);
|
|
37 | 37 |
}); |
38 | 38 |
path = @"e:\pithos\01New folder"; |
39 |
Assert.DoesNotThrow(() => |
|
40 |
{ |
|
41 |
workflow.RaiseChangeNotification(path); |
|
42 |
}); |
|
39 |
Assert.DoesNotThrow(() => NativeMethods.RaiseChangeNotification(path)); |
|
43 | 40 |
|
44 | 41 |
} |
45 | 42 |
|
b/trunk/Pithos.Core/Agents/NetworkAgent.cs | ||
---|---|---|
6 | 6 |
using System.IO; |
7 | 7 |
using System.Linq; |
8 | 8 |
using System.Text; |
9 |
using System.Threading; |
|
10 | 9 |
using System.Threading.Tasks; |
11 | 10 |
using Pithos.Interfaces; |
11 |
using Pithos.Network; |
|
12 | 12 |
|
13 | 13 |
namespace Pithos.Core.Agents |
14 | 14 |
{ |
... | ... | |
23 | 23 |
public IStatusNotification StatusNotification { get; set; } |
24 | 24 |
[Import] |
25 | 25 |
public ICloudClient CloudClient { get; set; } |
26 |
|
|
27 |
[Import] |
|
28 |
public FileAgent FileAgent {get;set;} |
|
29 |
|
|
30 |
/* |
|
26 | 31 |
[Import] |
27 | 32 |
public IPithosWorkflow Workflow { get; set; } |
33 |
*/ |
|
34 |
|
|
28 | 35 |
|
29 |
private string _rootPath; |
|
30 | 36 |
private string _pithosContainer; |
31 | 37 |
private string _trashContainer; |
32 | 38 |
|
39 |
private int _blockSize; |
|
40 |
private string _blockHash; |
|
33 | 41 |
|
34 |
public void Start(string rootPath,string pithosContainer,string trashContainer) |
|
42 |
|
|
43 |
public void Start(string pithosContainer, string trashContainer, int blockSize, string blockHash) |
|
35 | 44 |
{ |
36 |
if (String.IsNullOrWhiteSpace(rootPath)) |
|
37 |
throw new ArgumentNullException("rootPath"); |
|
38 | 45 |
if (String.IsNullOrWhiteSpace(pithosContainer)) |
39 | 46 |
throw new ArgumentNullException("pithosContainer"); |
40 | 47 |
if (String.IsNullOrWhiteSpace(trashContainer)) |
41 | 48 |
throw new ArgumentNullException("trashContainer"); |
42 | 49 |
Contract.EndContractBlock(); |
43 | 50 |
|
44 |
_rootPath = rootPath; |
|
45 | 51 |
_pithosContainer = pithosContainer; |
46 | 52 |
_trashContainer = trashContainer; |
53 |
_blockSize = blockSize; |
|
54 |
_blockHash = blockHash; |
|
55 |
|
|
47 | 56 |
|
48 | 57 |
_agent = Agent<CloudAction>.Start(inbox => |
49 | 58 |
{ |
... | ... | |
54 | 63 |
var process = message.ContinueWith(t => |
55 | 64 |
{ |
56 | 65 |
var action = t.Result; |
66 |
|
|
57 | 67 |
Process(action); |
58 | 68 |
inbox.DoAsync(loop); |
59 | 69 |
}); |
... | ... | |
73 | 83 |
}); |
74 | 84 |
} |
75 | 85 |
|
86 |
|
|
76 | 87 |
public void Post(CloudAction cloudAction) |
77 | 88 |
{ |
78 | 89 |
if (cloudAction == null) |
... | ... | |
82 | 93 |
_agent.Post(cloudAction); |
83 | 94 |
} |
84 | 95 |
|
96 |
class ObjectInfoByNameComparer:IEqualityComparer<ObjectInfo> |
|
97 |
{ |
|
98 |
public bool Equals(ObjectInfo x, ObjectInfo y) |
|
99 |
{ |
|
100 |
return x.Name.Equals(y.Name,StringComparison.InvariantCultureIgnoreCase); |
|
101 |
} |
|
102 |
|
|
103 |
public int GetHashCode(ObjectInfo obj) |
|
104 |
{ |
|
105 |
return obj.Name.ToLower().GetHashCode(); |
|
106 |
} |
|
107 |
} |
|
108 |
|
|
85 | 109 |
public Task ProcessRemoteFiles(string accountPath,DateTime? since=null) |
86 | 110 |
{ |
87 | 111 |
|
... | ... | |
94 | 118 |
|
95 | 119 |
var enqueueFiles = listObjects.ContinueWith(task => |
96 | 120 |
{ |
121 |
//ListObjects failed at this point, need to reschedule |
|
122 |
if (task.IsFaulted) |
|
123 |
{ |
|
124 |
Trace.TraceError("[FAIL] ListObjects in ProcessRemoteFiles with {0}",task.Exception); |
|
125 |
ProcessRemoteFiles(accountPath, since); |
|
126 |
return; |
|
127 |
} |
|
97 | 128 |
Trace.CorrelationManager.StartLogicalOperation("Listener"); |
98 | 129 |
Trace.TraceInformation("[LISTENER] Start Processing"); |
99 | 130 |
|
100 | 131 |
var remoteObjects = task.Result; |
101 | 132 |
|
102 |
var pithosDir = new DirectoryInfo(accountPath); |
|
133 |
var remote=(from info in remoteObjects |
|
134 |
let name=info.Name |
|
135 |
where !name.EndsWith(".ignore",StringComparison.InvariantCultureIgnoreCase) && |
|
136 |
!name.StartsWith("fragments/",StringComparison.InvariantCultureIgnoreCase) |
|
137 |
select info) |
|
138 |
.Distinct(new ObjectInfoByNameComparer()).ToDictionary(info=> info.Name.ToLower(), info=>info); |
|
139 |
|
|
140 |
var commonObjects = new List<Tuple<ObjectInfo, FileInfo>>(); |
|
141 |
var localFiles = new List<FileInfo>(); |
|
142 |
|
|
143 |
//In order to avoid multiple iterations over the files, we iterate only once |
|
144 |
foreach (var fileInfo in FileAgent.EnumerateFileInfos()) |
|
145 |
{ |
|
146 |
var relativeUrl=fileInfo.AsRelativeUrlTo(FileAgent.RootPath); |
|
147 |
//and remove any matching objects from the list, adding them to the commonObjects list |
|
148 |
if (remote.ContainsKey(relativeUrl)) |
|
149 |
{ |
|
150 |
commonObjects.Add(Tuple.Create(remote[relativeUrl], fileInfo)); |
|
151 |
remote.Remove(relativeUrl); |
|
152 |
} |
|
153 |
else |
|
154 |
//If there is no match we add them to the localFiles list |
|
155 |
localFiles.Add(fileInfo); |
|
156 |
} |
|
103 | 157 |
|
104 |
var remoteFiles = from info in remoteObjects
|
|
105 |
select info.Name.ToLower();
|
|
158 |
//At the end of the iteration, the *remote* list will contain the files that exist
|
|
159 |
//only on the server
|
|
106 | 160 |
|
107 |
var onlyLocal = from localFile in pithosDir.EnumerateFiles() |
|
108 |
where !remoteFiles.Contains(localFile.Name.ToLower()) |
|
109 |
select new CloudAction(CloudActionType.UploadUnconditional, localFile, ObjectInfo.Empty); |
|
161 |
//Local files should be uploaded |
|
162 |
var actionsForLocal = from localFile in localFiles |
|
163 |
select new CloudAction(CloudActionType.UploadUnconditional, |
|
164 |
localFile, |
|
165 |
ObjectInfo.Empty); |
|
110 | 166 |
|
111 |
var localNames = from info in pithosDir.EnumerateFiles() |
|
112 |
select info.Name.ToLower(); |
|
167 |
//Remote files should be downloaded |
|
168 |
var actionsForRemote = from dictPair in remote |
|
169 |
let upFile = dictPair.Value |
|
170 |
select new CloudAction(CloudActionType.DownloadUnconditional, null, upFile); |
|
113 | 171 |
|
114 |
var onlyRemote = from upFile in remoteObjects |
|
115 |
where !localNames.Contains(upFile.Name.ToLower()) |
|
116 |
select new CloudAction(CloudActionType.DownloadUnconditional, null, upFile); |
|
172 |
//Common files should be checked on a per-case basis to detect differences, which is newer |
|
173 |
var actionsForCommon = from pair in commonObjects |
|
174 |
let objectInfo = pair.Item1 |
|
175 |
let localFile = pair.Item2 |
|
176 |
select new CloudAction(CloudActionType.MustSynch, localFile, objectInfo); |
|
117 | 177 |
|
118 |
|
|
119 |
var commonObjects = from upFile in remoteObjects |
|
120 |
join localFile in pithosDir.EnumerateFiles() |
|
121 |
on upFile.Name.ToLower() equals localFile.Name.ToLower() |
|
122 |
select new CloudAction(CloudActionType.MustSynch, localFile, upFile); |
|
178 |
//Collect all the actions |
|
179 |
var allActions = actionsForLocal.Union(actionsForRemote).Union(actionsForCommon); |
|
123 | 180 |
|
124 |
var uniques =
|
|
125 |
onlyLocal.Union(onlyRemote).Union(commonObjects)
|
|
181 |
//And remove those that are already being processed by the agent
|
|
182 |
var distinctActions =allActions
|
|
126 | 183 |
.Except(_agent.GetEnumerable(), new PithosMonitor.LocalFileComparer()) |
127 | 184 |
.ToList(); |
128 | 185 |
|
129 |
_agent.AddFromEnumerable(uniques); |
|
186 |
//Queue all the actions |
|
187 |
_agent.AddFromEnumerable(distinctActions); |
|
130 | 188 |
|
131 |
StatusNotification.NotifyChange(String.Format("Processing {0} files", uniques.Count));
|
|
189 |
StatusNotification.NotifyChange(String.Format("Processing {0} files", distinctActions.Count));
|
|
132 | 190 |
|
133 | 191 |
Trace.TraceInformation("[LISTENER] End Processing"); |
134 | 192 |
Trace.CorrelationManager.StopLogicalOperation(); |
... | ... | |
145 | 203 |
{ |
146 | 204 |
Trace.TraceInformation("[LISTENER] Finished"); |
147 | 205 |
} |
148 |
ProcessRemoteFiles(accountPath,nextSince); |
|
206 |
ProcessRemoteFiles(accountPath, nextSince); |
|
207 |
|
|
149 | 208 |
}); |
150 | 209 |
return loop; |
151 | 210 |
} |
152 | 211 |
|
212 |
|
|
153 | 213 |
|
154 | 214 |
|
155 | 215 |
private void Process(CloudAction action) |
... | ... | |
162 | 222 |
var localFile = action.LocalFile; |
163 | 223 |
var cloudFile = action.CloudFile; |
164 | 224 |
var downloadPath = (cloudFile == null) ? String.Empty |
165 |
: Path.Combine(_rootPath, cloudFile.Name); |
|
225 |
: Path.Combine(FileAgent.RootPath, cloudFile.Name.RelativeUrlToFilePath()); |
|
226 |
|
|
166 | 227 |
try |
167 | 228 |
{ |
168 | 229 |
switch (action.Action) |
169 | 230 |
{ |
170 | 231 |
case CloudActionType.UploadUnconditional: |
171 |
UploadCloudFile(localFile.Name, localFile.FullName, action.LocalHash.Value);
|
|
232 |
UploadCloudFile(localFile, action.LocalHash.Value); |
|
172 | 233 |
break; |
173 | 234 |
case CloudActionType.DownloadUnconditional: |
174 |
DownloadCloudFile(_pithosContainer, cloudFile.Name, downloadPath);
|
|
235 |
DownloadCloudFile(_pithosContainer, new Uri(cloudFile.Name,UriKind.Relative), downloadPath);
|
|
175 | 236 |
break; |
176 | 237 |
case CloudActionType.DeleteCloud: |
177 | 238 |
DeleteCloudFile(cloudFile.Name); |
... | ... | |
194 | 255 |
//Maybe need to store version as well, to check who has the latest version |
195 | 256 |
|
196 | 257 |
//StatusKeeper.SetFileOverlayStatus(downloadPath, FileOverlayStatus.Conflict); |
197 |
UploadCloudFile(localFile.Name, localFile.FullName, action.LocalHash.Value);
|
|
258 |
UploadCloudFile(localFile, action.LocalHash.Value); |
|
198 | 259 |
} |
199 | 260 |
else |
200 | 261 |
{ |
... | ... | |
204 | 265 |
case FileStatus.Unchanged: |
205 | 266 |
//It he cloud file has a later date, it was modified by another user or computer. |
206 | 267 |
//If the local file's status is Unchanged, we should go on and download the cloud file |
207 |
DownloadCloudFile(_pithosContainer, action.CloudFile.Name, downloadPath);
|
|
268 |
DownloadCloudFile(_pithosContainer, new Uri(action.CloudFile.Name,UriKind.Relative), downloadPath);
|
|
208 | 269 |
break; |
209 | 270 |
case FileStatus.Modified: |
210 | 271 |
//If the local file is Modified, we may have a conflict. In this case we should mark the file as Conflict |
... | ... | |
230 | 291 |
} |
231 | 292 |
} |
232 | 293 |
else |
233 |
DownloadCloudFile(_pithosContainer, action.CloudFile.Name, downloadPath);
|
|
294 |
DownloadCloudFile(_pithosContainer, new Uri(action.CloudFile.Name,UriKind.Relative), downloadPath);
|
|
234 | 295 |
break; |
235 | 296 |
} |
236 | 297 |
Trace.TraceInformation("[ACTION] End Processing {0}:{1}->{2}", action.Action, action.LocalFile, action.CloudFile.Name); |
... | ... | |
267 | 328 |
|
268 | 329 |
this.StatusKeeper.SetFileStatus(newPath, FileStatus.Unchanged); |
269 | 330 |
this.StatusKeeper.SetFileOverlayStatus(newPath, FileOverlayStatus.Normal); |
270 |
Workflow.RaiseChangeNotification(newPath);
|
|
331 |
NativeMethods.RaiseChangeNotification(newPath);
|
|
271 | 332 |
} |
272 | 333 |
|
273 | 334 |
private void DeleteCloudFile(string fileName) |
... | ... | |
285 | 346 |
this.StatusKeeper.RemoveFileOverlayStatus(fileName); |
286 | 347 |
} |
287 | 348 |
|
288 |
private void DownloadCloudFile(string container, string fileName, string localPath) |
|
349 |
//Download a file. |
|
350 |
private void DownloadCloudFile(string container, Uri relativeUrl, string localPath) |
|
289 | 351 |
{ |
290 | 352 |
if (String.IsNullOrWhiteSpace(container)) |
291 | 353 |
throw new ArgumentNullException("container"); |
292 |
if (String.IsNullOrWhiteSpace(fileName)) |
|
293 |
throw new ArgumentNullException("fileName"); |
|
354 |
if (relativeUrl==null) |
|
355 |
throw new ArgumentNullException("relativeUrl"); |
|
356 |
|
|
294 | 357 |
if (String.IsNullOrWhiteSpace(localPath)) |
295 | 358 |
throw new ArgumentNullException("localPath"); |
296 | 359 |
if (!Path.IsPathRooted(localPath)) |
297 | 360 |
throw new ArgumentException("The localPath must be rooted", "localPath"); |
298 | 361 |
Contract.EndContractBlock(); |
299 | 362 |
|
300 |
var state = StatusKeeper.GetNetworkState(localPath); |
|
301 |
//Abort if the file is already being uploaded or downloaded |
|
302 |
if (state != NetworkState.None) |
|
363 |
var url = relativeUrl.ToString(); |
|
364 |
if (url.EndsWith(".ignore",StringComparison.InvariantCultureIgnoreCase)) |
|
303 | 365 |
return; |
304 | 366 |
|
305 |
StatusKeeper.SetNetworkState(localPath, NetworkState.Downloading); |
|
306 |
|
|
307 |
var tempPath = Path.GetTempFileName(); |
|
367 |
//Are we already downloading or uploading the file? |
|
368 |
using (var gate=NetworkGate.Acquire(localPath, NetworkOperation.Downloading)) |
|
369 |
{ |
|
370 |
if (gate.Failed) |
|
371 |
return; |
|
372 |
//Calculate the relative file path for the new file |
|
373 |
var relativePath = relativeUrl.RelativeUriToFilePath(); |
|
374 |
//The file will be stored in a temporary location while downloading with an extension .download |
|
375 |
var tempPath = Path.Combine(FileAgent.FragmentsPath, relativePath + ".download"); |
|
376 |
//The file's hashmap will be stored in the same location with the extension .hashmap |
|
377 |
var hashPath = Path.Combine(FileAgent.FragmentsPath, relativePath + ".hashmap"); |
|
378 |
|
|
379 |
//Retrieve the hashmap from the server |
|
380 |
var getHashMap = CloudClient.GetHashMap(container,url); |
|
381 |
//And save it |
|
382 |
var saveHashMap = getHashMap.ContinueWith(t => |
|
383 |
{ |
|
384 |
var treeHash = t.Result; |
|
385 |
treeHash.Save(hashPath); |
|
386 |
}); |
|
308 | 387 |
|
309 |
var getObject = CloudClient.GetObject(container, fileName, tempPath).ContinueWith(t => |
|
310 |
File.Move(tempPath, localPath)); |
|
388 |
//Make sure the target folder exists. DownloadFileTask will not create the folder |
|
389 |
var directoryPath=Path.GetDirectoryName(tempPath); |
|
390 |
if (!Directory.Exists(directoryPath)) |
|
391 |
Directory.CreateDirectory(directoryPath); |
|
311 | 392 |
|
312 |
var getInfo = getObject.ContinueWith(t => |
|
313 |
CloudClient.GetObjectInfo(container, fileName)); |
|
314 |
var storeInfo = getInfo.ContinueWith(t => |
|
315 |
StatusKeeper.StoreInfo(fileName, t.Result)); |
|
393 |
//Download the object to the temporary location |
|
394 |
var getObject = CloudClient.GetObject(container, url, tempPath).ContinueWith(t => |
|
395 |
{ |
|
396 |
//And move it to its actual location once downloading is finished |
|
397 |
if (File.Exists(localPath)) |
|
398 |
File.Replace(tempPath,localPath,null,true); |
|
399 |
else |
|
400 |
File.Move(tempPath,localPath); |
|
401 |
}); |
|
402 |
|
|
403 |
//Retrieve the object's metadata |
|
404 |
var getInfo = saveHashMap.ContinueWith(t => getObject).ContinueWith(t => |
|
405 |
CloudClient.GetObjectInfo(container, url)); |
|
406 |
//And store it |
|
407 |
var storeInfo = getInfo.ContinueWith(t => |
|
408 |
StatusKeeper.StoreInfo(localPath, t.Result)); |
|
316 | 409 |
|
317 |
storeInfo.Wait(); |
|
410 |
storeInfo.Wait();
|
|
318 | 411 |
|
319 |
StatusKeeper.SetNetworkState(localPath, NetworkState.None);
|
|
412 |
}
|
|
320 | 413 |
} |
321 | 414 |
|
322 |
private void UploadCloudFile(string fileName, string path, string hash)
|
|
415 |
private void UploadCloudFile(FileInfo fileInfo, string hash)
|
|
323 | 416 |
{ |
324 |
if (String.IsNullOrWhiteSpace(fileName)) |
|
325 |
throw new ArgumentNullException("fileName"); |
|
326 |
if (Path.IsPathRooted(fileName)) |
|
327 |
throw new ArgumentException("The fileName should not be rooted", "fileName"); |
|
328 |
if (String.IsNullOrWhiteSpace(path)) |
|
329 |
throw new ArgumentNullException("path"); |
|
417 |
if (fileInfo==null) |
|
418 |
throw new ArgumentNullException("fileInfo"); |
|
330 | 419 |
if (String.IsNullOrWhiteSpace(hash)) |
331 | 420 |
throw new ArgumentNullException("hash"); |
332 | 421 |
Contract.EndContractBlock(); |
333 | 422 |
|
334 |
var state = StatusKeeper.GetNetworkState(path); |
|
335 |
//Abort if the file is already being uploaded or downloaded |
|
336 |
if (state != NetworkState.None) |
|
423 |
if (fileInfo.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase)) |
|
337 | 424 |
return; |
425 |
|
|
426 |
var url = fileInfo.AsRelativeUrlTo(FileAgent.RootPath); |
|
338 | 427 |
|
339 |
StatusKeeper.SetNetworkState(path, NetworkState.Uploading); |
|
428 |
using(var gate=NetworkGate.Acquire(fileInfo.FullName,NetworkOperation.Uploading)) |
|
429 |
{ |
|
430 |
//Abort if the file is already being uploaded or downloaded |
|
431 |
if (gate.Failed) |
|
432 |
return; |
|
340 | 433 |
|
341 |
//Even if GetObjectInfo times out, we can proceed with the upload |
|
342 |
var info = CloudClient.GetObjectInfo(_pithosContainer, fileName); |
|
343 | 434 |
|
344 |
var putOrUpdate = Task.Factory.StartNew(() => |
|
345 |
{ |
|
346 |
if (!hash.Equals(info.Hash, StringComparison.InvariantCultureIgnoreCase)) |
|
435 |
//Even if GetObjectInfo times out, we can proceed with the upload |
|
436 |
var info = CloudClient.GetObjectInfo(_pithosContainer, url); |
|
437 |
|
|
438 |
//If the file hashes match, abort the upload |
|
439 |
if (hash.Equals(info.Hash, StringComparison.InvariantCultureIgnoreCase)) |
|
347 | 440 |
{ |
348 |
var setStatus = Task.Factory.StartNew(() =>
|
|
349 |
StatusKeeper.SetFileOverlayStatus(path, FileOverlayStatus.Modified));
|
|
350 |
var put = setStatus.ContinueWith(t =>
|
|
351 |
CloudClient.PutObject(_pithosContainer, fileName, path, hash));
|
|
441 |
//but store any metadata changes
|
|
442 |
this.StatusKeeper.StoreInfo(fileInfo.FullName, info);
|
|
443 |
Trace.TraceInformation("Skip upload of {0}, hashes match", fileInfo.FullName);
|
|
444 |
return;
|
|
352 | 445 |
} |
353 |
else |
|
446 |
|
|
447 |
//If the file was last modified by a hashmap operation, its hash will be a treehash |
|
448 |
//We must load the local file's treehash and check it against the object's hash |
|
449 |
//The hash will be stored under the fragments path, in the same relative path as to |
|
450 |
//the pithos root path |
|
451 |
var relativePath = fileInfo.AsRelativeTo(FileAgent.RootPath); |
|
452 |
var hashPath = Path.Combine(FileAgent.FragmentsPath, relativePath) + ".hashmap"; |
|
453 |
//Load the hash or calculate a new one |
|
454 |
var hashFileExists = File.Exists(hashPath); |
|
455 |
var treeHash = hashFileExists ? |
|
456 |
TreeHash.LoadTreeHash(hashPath, Guid.NewGuid()).Result |
|
457 |
: Signature.CalculateTreeHash(fileInfo.FullName, _blockSize, _blockHash); |
|
458 |
//If this is a new treehash, store it |
|
459 |
if (!hashFileExists) |
|
460 |
treeHash.Save(hashPath); |
|
461 |
|
|
462 |
var topHash = treeHash.TopHash.ToHashString(); |
|
463 |
if (topHash.Equals(info.Hash, StringComparison.InvariantCultureIgnoreCase)) |
|
354 | 464 |
{ |
355 |
this.StatusKeeper.StoreInfo(path, info); |
|
465 |
this.StatusKeeper.StoreInfo(fileInfo.FullName, info); |
|
466 |
Trace.TraceInformation("Skip upload of {0}, treehashes match", fileInfo.FullName); |
|
467 |
return; |
|
356 | 468 |
} |
357 |
}); |
|
358 |
putOrUpdate.Wait(); |
|
469 |
|
|
359 | 470 |
|
360 |
this.StatusKeeper.SetFileState(path, FileStatus.Unchanged, FileOverlayStatus.Normal); |
|
361 |
this.StatusKeeper.SetNetworkState(path, NetworkState.None); |
|
362 |
Workflow.RaiseChangeNotification(path); |
|
363 |
} |
|
364 | 471 |
|
365 | 472 |
|
366 |
}
|
|
473 |
//Does the object exist or not?
|
|
367 | 474 |
|
368 |
public class CloudAction |
|
369 |
{ |
|
370 |
public CloudActionType Action { get; set; } |
|
371 |
public FileInfo LocalFile { get; set; } |
|
372 |
public ObjectInfo CloudFile { get; set; } |
|
475 |
//Calculate or load the file's hashmap |
|
373 | 476 |
|
374 |
public Lazy<string> LocalHash { get; set; } |
|
477 |
/* |
|
478 |
if (fileInfo.Bytes > _blockSize) |
|
479 |
{ |
|
480 |
var hashPath = Path.Combine(FileAgent.FragmentsPath, relativePath) + ".hashmap"; |
|
481 |
|
|
482 |
//Load the existing hashPath, if we have one, or calculate a new one |
|
483 |
var treeHash = File.Exists(hashPath) ? |
|
484 |
TreeHash.LoadTreeHash(hashPath, Guid.NewGuid()).Result |
|
485 |
: Signature.CalculateTreeHash(fileInfo.FullName, _blockSize , _blockHash); |
|
486 |
|
|
487 |
//Do the Hashmap Put |
|
488 |
Task<string> putHashMap=CloudClient.PutHashMap(_pithosContainer,url, treeHash); |
|
489 |
putHashMap.Wait(); |
|
490 |
}*/ |
|
375 | 491 |
|
376 |
public string OldFileName { get; set; } |
|
377 |
public string OldPath { get; set; } |
|
378 |
public string NewFileName { get; set; } |
|
379 |
public string NewPath { get; set; } |
|
492 |
// |
|
380 | 493 |
|
381 |
public CloudAction(CloudActionType action, string oldPath, string oldFileName, string newFileName, string newPath) |
|
382 |
{ |
|
383 |
Action = action; |
|
384 |
OldFileName = oldFileName; |
|
385 |
OldPath = oldPath; |
|
386 |
NewFileName = newFileName; |
|
387 |
NewPath = newPath; |
|
388 |
} |
|
389 | 494 |
|
390 |
public CloudAction(CloudActionType action, FileInfo localFile, ObjectInfo cloudFile) |
|
391 |
{ |
|
392 |
Action = action; |
|
393 |
LocalFile = localFile; |
|
394 |
CloudFile = cloudFile; |
|
395 |
if (LocalFile != null) |
|
396 |
LocalHash = new Lazy<string>(() => Signature.CalculateMD5(LocalFile.FullName), LazyThreadSafetyMode.ExecutionAndPublication); |
|
495 |
var putOrUpdate = Task.Factory.StartNew(() => |
|
496 |
{ |
|
497 |
//Mark the file as modified while we upload it |
|
498 |
var setStatus = Task.Factory.StartNew(() => |
|
499 |
StatusKeeper.SetFileOverlayStatus(fileInfo.FullName,FileOverlayStatus.Modified)); |
|
500 |
//And then upload it |
|
501 |
var put = setStatus.ContinueWith(t => |
|
502 |
CloudClient.PutObject(_pithosContainer,url,fileInfo.FullName, hash)); |
|
503 |
}); |
|
504 |
putOrUpdate.Wait(); |
|
505 |
//If everything succeeds, change the file and overlay status to normal |
|
506 |
this.StatusKeeper.SetFileState(fileInfo.FullName, FileStatus.Unchanged, FileOverlayStatus.Normal); |
|
507 |
} |
|
508 |
//Notify the Shell to update the overlays |
|
509 |
NativeMethods.RaiseChangeNotification(fileInfo.FullName); |
|
397 | 510 |
} |
398 | 511 |
|
399 |
} |
|
400 | 512 |
|
401 |
public enum CloudActionType |
|
402 |
{ |
|
403 |
MustSynch, |
|
404 |
UploadUnconditional, |
|
405 |
DownloadUnconditional, |
|
406 |
DeleteLocal, |
|
407 |
DeleteCloud, |
|
408 |
RenameCloud |
|
409 | 513 |
} |
410 | 514 |
|
515 |
|
|
516 |
|
|
411 | 517 |
|
412 | 518 |
} |
b/trunk/Pithos.Core/Agents/WorkflowAgent.cs | ||
---|---|---|
18 | 18 |
public IStatusNotification StatusNotification { get; set; } |
19 | 19 |
[Import] |
20 | 20 |
public IStatusKeeper StatusKeeper { get; set; } |
21 |
[Import] |
|
22 |
public IPithosWorkflow Workflow { get; set; } |
|
21 |
|
|
22 |
//We should avoid processing files stored in the Fragments folder |
|
23 |
//The Full path to the fragments folder is stored in FragmentsPath |
|
24 |
public string FragmentsPath { get; set; } |
|
23 | 25 |
|
24 | 26 |
[Import] |
25 | 27 |
public NetworkAgent NetworkAgent { get; set; } |
... | ... | |
57 | 59 |
|
58 | 60 |
public void RestartInterruptedFiles() |
59 | 61 |
{ |
62 |
|
|
60 | 63 |
StatusNotification.NotifyChange("Restart processing interrupted files", TraceLevel.Verbose); |
61 | 64 |
var interruptedStates = new[] { FileOverlayStatus.Unversioned, FileOverlayStatus.Modified }; |
62 |
var filesQuery = from state in FileState.Queryable |
|
63 |
where interruptedStates.Contains(state.OverlayStatus) |
|
65 |
|
|
66 |
var pendingEntries = (from state in FileState.Queryable |
|
67 |
where interruptedStates.Contains(state.OverlayStatus) && |
|
68 |
!state.FilePath.StartsWith(FragmentsPath) && |
|
69 |
!state.FilePath.EndsWith(".ignore") |
|
70 |
select state).ToList(); |
|
71 |
var staleEntries = from state in pendingEntries |
|
72 |
where !File.Exists(state.FilePath) |
|
73 |
select state; |
|
74 |
var staleKeys = staleEntries.Select(state=>state.Id); |
|
75 |
FileState.DeleteAll(staleKeys); |
|
76 |
|
|
77 |
var validEntries = from state in pendingEntries.Except(staleEntries) |
|
78 |
where File.Exists(state.FilePath) |
|
64 | 79 |
select new WorkflowState |
65 | 80 |
{ |
66 | 81 |
Path = state.FilePath.ToLower(), |
... | ... | |
73 | 88 |
WatcherChangeTypes.Created : |
74 | 89 |
WatcherChangeTypes.Changed |
75 | 90 |
}; |
76 |
_agent.AddFromEnumerable(filesQuery); |
|
77 | 91 |
|
78 |
} |
|
92 |
_agent.AddFromEnumerable(validEntries); |
|
93 |
|
|
79 | 94 |
|
95 |
} |
|
80 | 96 |
|
81 | 97 |
private void Process(WorkflowState state) |
82 | 98 |
{ |
b/trunk/Pithos.Core/FileState.cs | ||
---|---|---|
4 | 4 |
// </copyright> |
5 | 5 |
// ----------------------------------------------------------------------- |
6 | 6 |
|
7 |
using System.IO; |
|
7 | 8 |
using System.Threading.Tasks; |
8 | 9 |
using Castle.ActiveRecord; |
9 | 10 |
using Castle.ActiveRecord.Framework; |
10 | 11 |
using NHibernate.Engine; |
11 | 12 |
using Pithos.Interfaces; |
13 |
using Pithos.Network; |
|
12 | 14 |
|
13 | 15 |
namespace Pithos.Core |
14 | 16 |
{ |
... | ... | |
48 | 50 |
[Property] |
49 | 51 |
public string TopHash { get; set; } |
50 | 52 |
|
51 |
// [HasMany(Table = "Tags", Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true)]
|
|
53 |
[HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true,Inverse=true)]
|
|
52 | 54 |
public IList<FileTag> Tags |
53 | 55 |
{ |
54 | 56 |
get { return _tags; } |
... | ... | |
77 | 79 |
|
78 | 80 |
public Task<FileState> UpdateHashesAsync() |
79 | 81 |
{ |
80 |
|
|
82 |
//Skip updating the hash for folders |
|
83 |
if (Directory.Exists(FilePath)) |
|
84 |
return Task.Factory.StartNew(() => this); |
|
85 |
|
|
81 | 86 |
return Task.Factory.StartNew(() => { Checksum = Signature.CalculateMD5(FilePath); }) |
82 | 87 |
.ContinueWith( |
83 | 88 |
t => this); |
b/trunk/Pithos.Core/IPithosWorkflow.cs | ||
---|---|---|
8 | 8 |
IStatusKeeper StatusKeeper { get; set; } |
9 | 9 |
FileStatus SetFileStatus(string path,FileStatus status); |
10 | 10 |
void ClearFileStatus(string path); |
11 |
void RaiseChangeNotification(string path); |
|
11 |
// void RaiseChangeNotification(string path);
|
|
12 | 12 |
|
13 | 13 |
|
14 | 14 |
} |
b/trunk/Pithos.Core/IStatusKeeper.cs | ||
---|---|---|
25 | 25 |
void StoreInfo(string path, ObjectInfo objectInfo); |
26 | 26 |
T GetStatus<T>(string path,Func<FileState,T> getter,T defaultValue ); |
27 | 27 |
void SetStatus(string path, Action<FileState> setter); |
28 |
void SetNetworkState(string fileName, NetworkState uploading); |
|
29 |
NetworkState GetNetworkState(string fileName); |
|
30 | 28 |
|
31 | 29 |
void StartProcessing(CancellationToken token); |
32 | 30 |
|
33 | 31 |
string BlockHash { get; set; } |
32 |
int BlockSize { get; set; } |
|
34 | 33 |
} |
35 | 34 |
|
36 | 35 |
[ContractClassFor(typeof(IStatusKeeper))] |
... | ... | |
115 | 114 |
Contract.Requires(setter != null); |
116 | 115 |
} |
117 | 116 |
|
118 |
public void SetNetworkState(string path, NetworkState uploading)
|
|
117 |
public void SetNetworkState(string path, NetworkOperation operation)
|
|
119 | 118 |
{ |
120 | 119 |
Contract.Requires(!String.IsNullOrWhiteSpace(path)); |
121 | 120 |
Contract.Requires(Path.IsPathRooted(path)); |
122 | 121 |
} |
123 | 122 |
|
124 |
public NetworkState GetNetworkState(string path)
|
|
123 |
public NetworkOperation GetNetworkState(string path)
|
|
125 | 124 |
{ |
126 | 125 |
Contract.Requires(!String.IsNullOrWhiteSpace(path)); |
127 | 126 |
|
128 |
return default(NetworkState);
|
|
127 |
return default(NetworkOperation);
|
|
129 | 128 |
} |
130 | 129 |
|
131 | 130 |
public void ClearFileStatus(string path) |
... | ... | |
142 | 141 |
Contract.Requires(token != null, "token can't be empty"); |
143 | 142 |
} |
144 | 143 |
|
145 |
public string BlockHash { get; set; } |
|
146 |
|
|
144 |
public abstract string BlockHash { get; set; }
|
|
145 |
public abstract int BlockSize { get; set; } |
|
147 | 146 |
} |
148 | 147 |
} |
b/trunk/Pithos.Core/InMemStatusChecker.cs | ||
---|---|---|
6 | 6 |
using System.Linq; |
7 | 7 |
using System.Threading; |
8 | 8 |
using Pithos.Interfaces; |
9 |
using Pithos.Network; |
|
9 | 10 |
|
10 | 11 |
namespace Pithos.Core |
11 | 12 |
{ |
... | ... | |
79 | 80 |
throw new NotImplementedException(); |
80 | 81 |
} |
81 | 82 |
|
82 |
ConcurrentDictionary<string, NetworkState> _networkState = new ConcurrentDictionary<string, NetworkState>();
|
|
83 |
ConcurrentDictionary<string, NetworkOperation> _networkState = new ConcurrentDictionary<string, NetworkOperation>();
|
|
83 | 84 |
|
84 | 85 |
|
85 |
public void SetNetworkState(string path, NetworkState state)
|
|
86 |
public void SetNetworkState(string path, NetworkOperation operation)
|
|
86 | 87 |
{ |
87 |
_networkState[path.ToLower()] = state;
|
|
88 |
_networkState[path.ToLower()] = operation;
|
|
88 | 89 |
//Removing may fail so we store the "None" value anyway |
89 |
if (state == NetworkState.None)
|
|
90 |
if (operation == NetworkOperation.None)
|
|
90 | 91 |
{ |
91 |
NetworkState oldState;
|
|
92 |
_networkState.TryRemove(path, out oldState);
|
|
92 |
NetworkOperation oldOperation;
|
|
93 |
_networkState.TryRemove(path, out oldOperation);
|
|
93 | 94 |
} |
94 | 95 |
} |
95 | 96 |
|
96 |
public NetworkState GetNetworkState(string path)
|
|
97 |
public NetworkOperation GetNetworkState(string path)
|
|
97 | 98 |
{ |
98 |
NetworkState state;
|
|
99 |
if (_networkState.TryGetValue(path, out state))
|
|
100 |
return state;
|
|
101 |
return NetworkState.None;
|
|
99 |
NetworkOperation operation;
|
|
100 |
if (_networkState.TryGetValue(path, out operation))
|
|
101 |
return operation;
|
|
102 |
return NetworkOperation.None;
|
|
102 | 103 |
} |
103 | 104 |
|
104 | 105 |
public void StartProcessing(CancellationToken token) |
... | ... | |
108 | 109 |
|
109 | 110 |
public string BlockHash { get; set; } |
110 | 111 |
|
112 |
public int BlockSize { get; set; } |
|
113 |
|
|
114 |
|
|
111 | 115 |
public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus) |
112 | 116 |
{ |
113 | 117 |
_overlayCache[path] = overlayStatus; |
b/trunk/Pithos.Core/NativeMethods.cs | ||
---|---|---|
1 | 1 |
๏ปฟusing System; |
2 | 2 |
using System.Collections.Generic; |
3 |
using System.IO; |
|
3 | 4 |
using System.Linq; |
4 | 5 |
using System.Runtime.InteropServices; |
5 | 6 |
using System.Text; |
... | ... | |
26 | 27 |
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.</para> |
27 | 28 |
/// </remarks> |
28 | 29 |
[Flags] |
29 |
enum HChangeNotifyEventID |
|
30 |
public enum HChangeNotifyEventID
|
|
30 | 31 |
{ |
31 | 32 |
/// <summary> |
32 | 33 |
/// All events have occurred. |
... | ... | |
268 | 269 |
|
269 | 270 |
#endregion |
270 | 271 |
|
271 |
|
|
272 |
internal static class NativeMethods |
|
272 |
public static class NativeMethods |
|
273 | 273 |
{ |
274 | 274 |
[DllImport("shell32.dll")] |
275 | 275 |
public static extern void SHChangeNotify(HChangeNotifyEventID wEventId, |
... | ... | |
277 | 277 |
IntPtr dwItem1, |
278 | 278 |
IntPtr dwItem2); |
279 | 279 |
|
280 |
public static void RaiseChangeNotification(string path) |
|
281 |
{ |
|
282 |
if (String.IsNullOrWhiteSpace(path)) |
|
283 |
throw new ArgumentNullException("path", "The path parameter must not be emtpy"); |
|
284 |
|
|
285 |
if (!Directory.Exists(path) && !File.Exists(path)) |
|
286 |
throw new FileNotFoundException("The specified file or path does not exist", path); |
|
287 |
|
|
288 |
|
|
289 |
IntPtr pathPointer = Marshal.StringToCoTaskMemAuto(path); |
|
290 |
|
|
291 |
try |
|
292 |
{ |
|
293 |
NativeMethods.SHChangeNotify(HChangeNotifyEventID.SHCNE_UPDATEITEM, |
|
294 |
HChangeNotifyFlags.SHCNF_PATHW | HChangeNotifyFlags.SHCNF_FLUSHNOWAIT, |
|
295 |
pathPointer, IntPtr.Zero); |
|
296 |
} |
|
297 |
finally |
|
298 |
{ |
|
299 |
Marshal.FreeHGlobal(pathPointer); |
|
300 |
} |
|
301 |
|
|
302 |
} |
|
280 | 303 |
} |
304 |
|
|
305 |
|
|
281 | 306 |
} |
b/trunk/Pithos.Core/Pithos.Core.csproj | ||
---|---|---|
128 | 128 |
</ItemGroup> |
129 | 129 |
<ItemGroup> |
130 | 130 |
<Compile Include="Agent.cs" /> |
131 |
<Compile Include="Agents\FileWatcherAgent.cs" /> |
|
131 |
<Compile Include="Agents\CloudAction.cs" /> |
|
132 |
<Compile Include="Agents\FileAgent.cs" /> |
|
133 |
<Compile Include="Agents\FileInfoExtensions.cs" /> |
|
132 | 134 |
<Compile Include="Agents\NetworkAgent.cs" /> |
133 | 135 |
<Compile Include="Agents\WorkflowAgent.cs" /> |
134 | 136 |
<Compile Include="DynamicDictionary.cs" /> |
... | ... | |
136 | 138 |
<Compile Include="FileState.cs" /> |
137 | 139 |
<Compile Include="IStatusService.cs" /> |
138 | 140 |
<Compile Include="JobQueue.cs" /> |
139 |
<Compile Include="Signature.cs" />
|
|
141 |
<Compile Include="NetworkGate.cs" />
|
|
140 | 142 |
<Compile Include="StatusKeeper.cs" /> |
141 | 143 |
<Compile Include="IPithosWorkflow.cs" /> |
142 | 144 |
<Compile Include="IStatusKeeper.cs" /> |
... | ... | |
147 | 149 |
<Compile Include="InMemStatusChecker.cs" /> |
148 | 150 |
<Compile Include="StatusInfo.cs" /> |
149 | 151 |
<Compile Include="StatusService.cs" /> |
150 |
<Compile Include="TreeHash.cs" /> |
|
151 | 152 |
<Compile Include="WorkflowState.cs" /> |
152 | 153 |
</ItemGroup> |
153 | 154 |
<ItemGroup> |
b/trunk/Pithos.Core/PithosMonitor.cs | ||
---|---|---|
17 | 17 |
using Pithos.Core.Agents; |
18 | 18 |
using Pithos.Interfaces; |
19 | 19 |
using System.ServiceModel; |
20 |
using Pithos.Network; |
|
20 | 21 |
|
21 | 22 |
namespace Pithos.Core |
22 | 23 |
{ |
... | ... | |
26 | 27 |
private const string PithosContainer = "pithos"; |
27 | 28 |
private const string TrashContainer = "trash"; |
28 | 29 |
|
30 |
private const string FragmentsFolder = "fragments"; |
|
31 |
|
|
32 |
private int _blockSize; |
|
33 |
private string _blockHash; |
|
34 |
|
|
29 | 35 |
[Import] |
30 | 36 |
public IPithosSettings Settings{get;set;} |
31 | 37 |
|
... | ... | |
44 | 50 |
public IStatusNotification StatusNotification { get; set; } |
45 | 51 |
|
46 | 52 |
[Import] |
47 |
public FileWatcherAgent FileWatcherAgent { get; set; }
|
|
53 |
public FileAgent FileAgent { get; set; }
|
|
48 | 54 |
|
49 | 55 |
[Import] |
50 | 56 |
public WorkflowAgent WorkflowAgent { get; set; } |
... | ... | |
61 | 67 |
|
62 | 68 |
public bool Pause |
63 | 69 |
{ |
64 |
get { return FileWatcherAgent.Pause; }
|
|
70 |
get { return FileAgent.Pause; } |
|
65 | 71 |
set |
66 | 72 |
{ |
67 |
FileWatcherAgent.Pause = value;
|
|
73 |
FileAgent.Pause = value; |
|
68 | 74 |
if (value) |
69 | 75 |
{ |
70 | 76 |
StatusKeeper.SetPithosStatus(PithosStatus.SyncPaused); |
... | ... | |
101 | 107 |
|
102 | 108 |
EnsurePithosContainers(); |
103 | 109 |
|
104 |
StatusKeeper.BlockHash = "sha256"; |
|
110 |
StatusKeeper.BlockHash = _blockHash; |
|
111 |
StatusKeeper.BlockSize = _blockSize; |
|
105 | 112 |
|
106 | 113 |
StatusKeeper.StartProcessing(_cancellationSource.Token); |
107 | 114 |
IndexLocalFiles(RootPath); |
... | ... | |
117 | 124 |
CloudClient.AuthenticationUrl = this.AuthenticationUrl; |
118 | 125 |
CloudClient.Authenticate(UserName, ApiKey); |
119 | 126 |
|
120 |
var pithosContainers = new[] {PithosContainer, TrashContainer};
|
|
127 |
var pithosContainers = new[] { TrashContainer,PithosContainer};
|
|
121 | 128 |
foreach (var container in pithosContainers) |
122 | 129 |
{ |
123 |
if (!CloudClient.ContainerExists(container)) |
|
124 |
CloudClient.CreateContainer(container); |
|
130 |
var info=CloudClient.GetContainerInfo(container); |
|
131 |
if (info == ContainerInfo.Empty) |
|
132 |
{ |
|
133 |
CloudClient.CreateContainer(container); |
|
134 |
info = CloudClient.GetContainerInfo(container); |
|
135 |
} |
|
136 |
_blockSize = info.BlockSize; |
|
137 |
_blockHash = info.BlockHash; |
|
125 | 138 |
} |
139 |
|
|
140 |
var allContainers= CloudClient.ListContainers(); |
|
141 |
var extraContainers = from container in allContainers |
|
142 |
where !pithosContainers.Contains(container.Name.ToLower()) |
|
143 |
select container; |
|
144 |
|
|
145 |
|
|
146 |
|
|
126 | 147 |
} |
127 | 148 |
|
128 | 149 |
public string AuthenticationUrl { get; set; } |
... | ... | |
152 | 173 |
Trace.TraceInformation("[START] Index Local"); |
153 | 174 |
try |
154 | 175 |
{ |
176 |
var fragmentsPath=Path.Combine(RootPath, FragmentsFolder); |
|
155 | 177 |
var files = |
156 | 178 |
from filePath in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).AsParallel() |
179 |
where !filePath.StartsWith(fragmentsPath,StringComparison.InvariantCultureIgnoreCase) && |
|
180 |
!filePath.EndsWith(".ignore",StringComparison.InvariantCultureIgnoreCase) |
|
157 | 181 |
select filePath.ToLower(); |
158 | 182 |
StatusKeeper.StoreUnversionedFiles(files); |
159 | 183 |
|
... | ... | |
227 | 251 |
StartNetworkAgent(RootPath); |
228 | 252 |
|
229 | 253 |
WorkflowAgent.StatusNotification = StatusNotification; |
254 |
WorkflowAgent.FragmentsPath = Path.Combine(RootPath, FragmentsFolder); |
|
230 | 255 |
WorkflowAgent.Start(); |
231 | 256 |
} |
232 | 257 |
catch (Exception) |
... | ... | |
271 | 296 |
private void StartNetworkAgent(string accountPath) |
272 | 297 |
{ |
273 | 298 |
NetworkAgent.StatusNotification = StatusNotification; |
274 |
|
|
275 |
NetworkAgent.Start(RootPath, PithosContainer,TrashContainer);
|
|
299 |
|
|
300 |
NetworkAgent.Start(PithosContainer, TrashContainer,_blockSize,_blockHash);
|
|
276 | 301 |
|
277 | 302 |
NetworkAgent.ProcessRemoteFiles(accountPath); |
278 | 303 |
} |
279 | 304 |
|
305 |
//Make sure a hidden fragments folder exists to store partial downloads |
|
306 |
private static string CreateHiddenFolder(string rootPath, string folderName) |
|
307 |
{ |
|
308 |
if (String.IsNullOrWhiteSpace(rootPath)) |
|
309 |
throw new ArgumentNullException("rootPath"); |
|
310 |
if (!Path.IsPathRooted(rootPath)) |
|
311 |
throw new ArgumentException("rootPath"); |
|
312 |
if (String.IsNullOrWhiteSpace(folderName)) |
|
313 |
throw new ArgumentNullException("folderName"); |
|
314 |
Contract.EndContractBlock(); |
|
315 |
|
|
316 |
var folder = Path.Combine(rootPath, folderName); |
|
317 |
if (!Directory.Exists(folder)) |
|
318 |
{ |
|
319 |
var info = Directory.CreateDirectory(folder); |
|
320 |
info.Attributes |= FileAttributes.Hidden; |
|
321 |
|
|
322 |
Trace.TraceInformation("Created Fragments Folder: {0}", folder); |
|
323 |
} |
|
324 |
return folder; |
|
325 |
} |
|
326 |
|
|
280 | 327 |
|
281 | 328 |
|
282 | 329 |
|
283 | 330 |
private void StartWatcherAgent(string path) |
284 | 331 |
{ |
285 |
FileWatcherAgent.StatusKeeper = StatusKeeper; |
|
286 |
FileWatcherAgent.Workflow = Workflow; |
|
287 |
FileWatcherAgent.Start(path); |
|
332 |
FileAgent.StatusKeeper = StatusKeeper; |
|
333 |
FileAgent.Workflow = Workflow; |
|
334 |
FileAgent.FragmentsPath = Path.Combine(RootPath, FragmentsFolder); |
|
335 |
FileAgent.Start(path); |
|
288 | 336 |
} |
289 | 337 |
|
290 | 338 |
public void Stop() |
291 | 339 |
{ |
292 |
FileWatcherAgent.Stop();
|
|
340 |
FileAgent.Stop(); |
|
293 | 341 |
if (timer != null) |
294 | 342 |
timer.Dispose(); |
295 | 343 |
timer = null; |
b/trunk/Pithos.Core/PithosWorkflow.cs | ||
---|---|---|
8 | 8 |
using System.Threading.Tasks; |
9 | 9 |
using Pithos.Interfaces; |
10 | 10 |
using System.IO; |
11 |
using System.Diagnostics; |
|
11 | 12 |
|
12 | 13 |
namespace Pithos.Core |
13 | 14 |
{ |
... | ... | |
22 | 23 |
|
23 | 24 |
public FileStatus SetFileStatus(string path, FileStatus status) |
24 | 25 |
{ |
26 |
Debug.Assert(!path.Contains("fragments")); |
|
27 |
Debug.Assert(!path.EndsWith(".ignore",StringComparison.InvariantCultureIgnoreCase)); |
|
28 |
|
|
25 | 29 |
if (String.IsNullOrWhiteSpace(path)) |
26 | 30 |
throw new ArgumentNullException("path", "The path parameter must not be emtpy"); |
27 | 31 |
|
... | ... | |
57 | 61 |
|
58 | 62 |
StatusKeeper.ClearFileStatus(path); |
59 | 63 |
} |
60 |
|
|
61 |
public void RaiseChangeNotification(string path) |
|
62 |
{ |
|
63 |
if (String.IsNullOrWhiteSpace(path)) |
|
64 |
throw new ArgumentNullException("path", "The path parameter must not be emtpy"); |
|
65 |
|
|
66 |
if (!Directory.Exists(path ) && !File.Exists(path)) |
|
67 |
throw new FileNotFoundException("The specified file or path does not exist",path); |
|
68 |
|
|
69 |
|
|
70 |
IntPtr pathPointer = Marshal.StringToCoTaskMemAuto(path); |
|
71 |
|
|
72 |
try |
|
73 |
{ |
|
74 |
NativeMethods.SHChangeNotify(HChangeNotifyEventID.SHCNE_UPDATEITEM, |
|
75 |
HChangeNotifyFlags.SHCNF_PATHW | HChangeNotifyFlags.SHCNF_FLUSHNOWAIT, |
|
76 |
pathPointer, IntPtr.Zero); |
|
77 |
} |
|
78 |
finally |
|
79 |
{ |
|
80 |
Marshal.FreeHGlobal(pathPointer); |
|
81 |
} |
|
82 |
|
|
83 |
} |
|
64 |
|
|
84 | 65 |
|
85 | 66 |
public Task<FileStream> OpenStreamWithWaiting(string path) |
86 | 67 |
{ |
b/trunk/Pithos.Core/StatusKeeper.cs | ||
---|---|---|
1 | 1 |
using System; |
2 | 2 |
using System.Collections; |
3 |
using System.Collections.Concurrent; |
|
4 | 3 |
using System.Collections.Generic; |
5 | 4 |
using System.ComponentModel.Composition; |
6 | 5 |
using System.Diagnostics; |
... | ... | |
17 | 16 |
using NHibernate.Criterion; |
18 | 17 |
using NHibernate.Impl; |
19 | 18 |
using Pithos.Interfaces; |
19 |
using Pithos.Network; |
|
20 | 20 |
|
21 | 21 |
namespace Pithos.Core |
22 | 22 |
{ |
... | ... | |
44 | 44 |
; |
45 | 45 |
if (!File.Exists(Path.Combine(_pithosDataPath ,"pithos.db"))) |
46 | 46 |
ActiveRecordStarter.CreateSchema(); |
47 |
|
|
48 |
CleanupStaleStates(); |
|
47 | 49 |
|
48 |
BlockHash = "sha256"; |
|
49 |
BlockSize = 4194304; |
|
50 |
} |
|
50 | 51 |
|
52 |
private void CleanupStaleStates() |
|
53 |
{ |
|
54 |
/*var stales = from state in FileState.Queryable |
|
55 |
where state.FilePath.StartsWith(@"e:\pithos\fragments") |
|
56 |
select state.Id;*/ |
|
57 |
FileState.DeleteAll(@"FilePath like 'e:\pithos\fragments%'"); |
|
58 |
; |
|
51 | 59 |
} |
52 | 60 |
|
53 | 61 |
private static InPlaceConfigurationSource GetConfiguration(string pithosDbPath) |
... | ... | |
89 | 97 |
job.ContinueWith(t => |
90 | 98 |
{ |
91 | 99 |
var action = job.Result; |
92 |
action(); |
|
100 |
try |
|
101 |
{ |
|
102 |
action(); |
|
103 |
} |
|
104 |
catch (Exception ex) |
|
105 |
{ |
|
106 |
Trace.TraceError("[ERROR] STATE \n{0}",ex); |
|
107 |
} |
|
93 | 108 |
queue.DoAsync(loop); |
94 | 109 |
}); |
95 | 110 |
}; |
... | ... | |
163 | 178 |
} |
164 | 179 |
|
165 | 180 |
|
166 |
ConcurrentDictionary<string,NetworkState> _networkState=new ConcurrentDictionary<string, NetworkState>(); |
|
167 | 181 |
private string _pithosDataPath; |
168 | 182 |
|
169 |
public void SetNetworkState(string path,NetworkState state) |
|
170 |
{ |
|
171 |
if (String.IsNullOrWhiteSpace(path)) |
|
172 |
throw new ArgumentNullException("path"); |
|
173 |
if (!Path.IsPathRooted(path)) |
|
174 |
throw new ArgumentException("path must be a rooted path","path"); |
|
175 |
Contract.EndContractBlock(); |
|
176 |
|
|
177 |
_networkState[path.ToLower()] = state; |
|
178 |
//Removing may fail so we store the "None" value anyway |
|
179 |
if (state == NetworkState.None) |
|
180 |
{ |
|
181 |
NetworkState oldState; |
|
182 |
_networkState.TryRemove(path, out oldState); |
|
183 |
} |
|
184 |
} |
|
185 |
|
|
186 |
public NetworkState GetNetworkState(string path) |
|
187 |
{ |
|
188 |
if (String.IsNullOrWhiteSpace(path)) |
|
189 |
throw new ArgumentNullException("path"); |
|
190 |
if (!Path.IsPathRooted(path)) |
|
191 |
throw new ArgumentException("path must be a rooted path", "path"); |
|
192 |
Contract.EndContractBlock(); |
|
193 |
|
|
194 |
NetworkState state; |
|
195 |
if (_networkState.TryGetValue(path, out state)) |
|
196 |
return state; |
|
197 |
return NetworkState.None; |
|
198 |
} |
|
199 |
|
|
200 | 183 |
public T GetStatus<T>(string path,Func<FileState,T> getter,T defaultValue ) |
201 | 184 |
{ |
202 | 185 |
if (String.IsNullOrWhiteSpace(path)) |
... | ... | |
233 | 216 |
|
234 | 217 |
_persistenceAgent.Post(() => |
235 | 218 |
{ |
236 |
var filePath = path.ToLower(); |
|
237 |
var state = FileState.Queryable.FirstOrDefault(s => s.FilePath == filePath); |
|
238 |
if (state != null) |
|
219 |
using (new SessionScope()) |
|
239 | 220 |
{ |
240 |
setter(state); |
|
241 |
state.Save(); |
|
242 |
} |
|
243 |
else |
|
244 |
{ |
|
245 |
state = new FileState {FilePath = filePath}; |
|
246 |
setter(state); |
|
247 |
state.Save(); |
|
221 |
var filePath = path.ToLower(); |
|
222 |
var state = FileState.Queryable.FirstOrDefault(s => s.FilePath == filePath); |
|
223 |
if (state != null) |
|
224 |
{ |
|
225 |
setter(state); |
|
226 |
state.Save(); |
|
227 |
} |
|
228 |
else |
|
229 |
{ |
|
230 |
state = new FileState {FilePath = filePath}; |
|
231 |
setter(state); |
|
232 |
state.Save(); |
|
233 |
} |
|
248 | 234 |
} |
249 | 235 |
}); |
250 | 236 |
} |
... | ... | |
256 | 242 |
/// <param name="setter"></param> |
257 | 243 |
private void UpdateStatus(string path, Action<FileState> setter) |
258 | 244 |
{ |
245 |
Debug.Assert(!path.Contains("fragments")); |
|
246 |
Debug.Assert(!path.EndsWith(".ignore")); |
|
247 |
|
|
259 | 248 |
if (String.IsNullOrWhiteSpace(path)) |
260 | 249 |
throw new ArgumentNullException("path", "path can't be empty"); |
261 | 250 |
|
... | ... | |
264 | 253 |
|
265 | 254 |
_persistenceAgent.Post(() => |
266 | 255 |
{ |
267 |
var filePath = path.ToLower(); |
|
268 |
|
|
269 |
var state = FileState.Queryable.FirstOrDefault(s=> s.FilePath == filePath); |
|
270 |
if (state == null) |
|
256 |
using (new SessionScope()) |
|
271 | 257 |
{ |
272 |
Trace.TraceWarning("[NOFILE] Unable to set status for {0}.", filePath); |
|
273 |
return; |
|
258 |
var filePath = path.ToLower(); |
|
259 |
|
|
260 |
var state = FileState.Queryable.FirstOrDefault(s => s.FilePath == filePath); |
|
261 |
if (state == null) |
|
262 |
{ |
|
263 |
Trace.TraceWarning("[NOFILE] Unable to set status for {0}.", filePath); |
|
264 |
return; |
|
265 |
} |
|
266 |
setter(state); |
|
267 |
state.Save(); |
|
274 | 268 |
} |
275 |
setter(state); |
|
276 |
state.Save(); |
|
269 |
|
|
277 | 270 |
}); |
278 | 271 |
} |
279 | 272 |
|
... | ... | |
345 | 338 |
throw new ArgumentNullException("path", "path can't be empty"); |
346 | 339 |
if (objectInfo==null) |
347 | 340 |
throw new ArgumentNullException("objectInfo", "objectInfo can't be empty"); |
348 |
|
|
349 |
var state = FileState.Queryable.FirstOrDefault(s => s.FilePath == path); |
|
350 |
state = state??new FileState(); |
|
351 |
|
|
352 |
state.FilePath = path.ToLower(); |
|
353 |
state.Checksum = objectInfo.Hash; |
|
354 |
state.FileStatus = FileStatus.Unchanged; |
|
355 |
state.OverlayStatus = FileOverlayStatus.Normal; |
|
356 |
foreach (var tag in objectInfo.Tags) |
|
341 |
|
|
342 |
|
|
343 |
_persistenceAgent.Post(() => |
|
357 | 344 |
{ |
358 |
state.Tags.Add(new FileTag |
|
359 |
{ |
|
360 |
FileState=state, |
|
361 |
Value=tag.Value |
|
362 |
}); |
|
363 |
} |
|
364 |
|
|
365 |
state.Save(); |
|
366 |
|
|
345 |
var filePath = path.ToLower(); |
|
346 |
//Load the existing files state and set its properties in one session |
|
347 |
using (new SessionScope()) |
|
348 |
{ |
|
349 |
//Forgetting to use a sessionscope results in two sessions being created, one by |
|
350 |
//FirstOrDefault and one by Save() |
|
351 |
var state = |
|
352 |
FileState.Queryable.FirstOrDefault(s => s.FilePath == filePath); |
|
353 |
//Create a new empty state object if this is a new file |
|
354 |
state = state ?? new FileState(); |
|
355 |
|
|
356 |
state.FilePath = filePath; |
|
357 |
state.Checksum = objectInfo.Hash; |
|
358 |
state.FileStatus = FileStatus.Unchanged; |
|
359 |
state.OverlayStatus = FileOverlayStatus.Normal; |
|
360 |
//Create a list of tags from the ObjectInfo's tag dictionary |
|
361 |
//Make sure to bind each tag to its parent state so we don't have to save each tag separately |
|
362 |
//state.Tags = (from pair in objectInfo.Tags |
|
363 |
// select |
|
364 |
// new FileTag |
|
365 |
// { |
|
366 |
// FileState = state, |
|
367 |
// Name = pair.Key, |
|
368 |
// Value = pair.Value |
|
369 |
// } |
|
370 |
// ).ToList(); |
|
371 |
|
|
372 |
//Do the save |
|
373 |
state.Save(); |
|
374 |
} |
|
375 |
}); |
|
376 |
|
|
367 | 377 |
} |
368 | 378 |
|
369 | 379 |
|
... | ... | |
386 | 396 |
} |
387 | 397 |
|
388 | 398 |
public void UpdateFileChecksum(string path, string checksum) |
Also available in: Unified diff