Revision 4f6d51d4
b/trunk/Pithos.Client.WPF/FileProperties/FilePropertiesView.xaml | ||
---|---|---|
96 | 96 |
</TabItem> |
97 | 97 |
<TabItem Header="Permissions"> |
98 | 98 |
<StackPanel> |
99 |
<TextBlock Margin="5" Visibility="{Binding Path=IsPublic,FallbackValue=false, Converter={StaticResource BoolToVisible}}">
|
|
99 |
<TextBlock Margin="5" Visibility="{Binding Path=IsPublic,FallbackValue=Collapsed, Converter={StaticResource BoolToVisible}}">
|
|
100 | 100 |
<Run Text="Public URL:" /> |
101 | 101 |
<Run Text="{Binding PublicUrl,FallbackValue='http://someurl'}" /> |
102 | 102 |
</TextBlock> |
b/trunk/Pithos.Client.WPF/FileProperties/FilePropertiesViewModel.cs | ||
---|---|---|
12 | 12 |
using System.Diagnostics; |
13 | 13 |
using System.Diagnostics.Contracts; |
14 | 14 |
using System.Drawing; |
15 |
using System.IO; |
|
15 | 16 |
using System.Net; |
16 | 17 |
using System.Threading.Tasks; |
17 | 18 |
using System.Windows; |
... | ... | |
324 | 325 |
if (IsPublic) |
325 | 326 |
PublicUrl = String.Format("{0}/v1{1}", Shell.Accounts.First(account=>account.UserName==PithosFile.Account).SiteUri,value.PublicUrl); |
326 | 327 |
|
327 |
using (var icon = Icon.ExtractAssociatedIcon(LocalFileName))
|
|
328 |
if (Directory.Exists(LocalFileName))
|
|
328 | 329 |
{ |
329 |
FileIcon = Imaging.CreateBitmapSourceFromHIcon(icon.Handle, Int32Rect.Empty, |
|
330 |
BitmapSizeOptions.FromEmptyOptions()); |
|
330 |
FileIcon= new BitmapImage(new Uri("../Images/Folder.ico",UriKind.Relative)); |
|
331 |
} |
|
332 |
else if (File.Exists(LocalFileName)) |
|
333 |
{ |
|
334 |
using (var icon = Icon.ExtractAssociatedIcon(LocalFileName)) |
|
335 |
{ |
|
336 |
FileIcon = Imaging.CreateBitmapSourceFromHIcon(icon.Handle, Int32Rect.Empty, |
|
337 |
BitmapSizeOptions.FromEmptyOptions()); |
|
338 |
} |
|
331 | 339 |
} |
332 | 340 |
NotifyOfPropertyChange(()=>PithosFile); |
333 | 341 |
IsBusy = false; |
b/trunk/Pithos.Client.WPF/Pithos.Client.WPF.csproj | ||
---|---|---|
70 | 70 |
<AssemblyOriginatorKeyFile>pithos.snk</AssemblyOriginatorKeyFile> |
71 | 71 |
</PropertyGroup> |
72 | 72 |
<PropertyGroup> |
73 |
<ApplicationIcon>Images\Tray.ico</ApplicationIcon>
|
|
73 |
<ApplicationIcon>Images\Pithos.ico</ApplicationIcon>
|
|
74 | 74 |
</PropertyGroup> |
75 | 75 |
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Premium Debug|x86'"> |
76 | 76 |
<DebugSymbols>true</DebugSymbols> |
... | ... | |
228 | 228 |
<Reference Include="WindowsBase" /> |
229 | 229 |
<Reference Include="PresentationCore" /> |
230 | 230 |
<Reference Include="PresentationFramework" /> |
231 |
<Reference Include="WPFToolkit.Extended"> |
|
231 |
<Reference Include="WPFToolkit.Extended, Version=1.5.0.0, Culture=neutral, PublicKeyToken=3e4669d2f30244f4, processorArchitecture=MSIL"> |
|
232 |
<SpecificVersion>False</SpecificVersion> |
|
232 | 233 |
<HintPath>..\packages\Extended.Wpf.Toolkit.1.5.0\lib\net40\WPFToolkit.Extended.dll</HintPath> |
233 | 234 |
</Reference> |
234 | 235 |
</ItemGroup> |
b/trunk/Pithos.Client.WPF/Properties/AssemblyInfo.cs | ||
---|---|---|
15 | 15 |
[assembly: AssemblyCopyright("Copyright © GRNet 2011")] |
16 | 16 |
[assembly: AssemblyTrademark("")] |
17 | 17 |
[assembly: AssemblyCulture("")] |
18 |
[assembly: AssemblyInformationalVersion("2011-12-15")]
|
|
18 |
[assembly: AssemblyInformationalVersion("2012-01-08")]
|
|
19 | 19 |
|
20 | 20 |
// Setting ComVisible to false makes the types in this assembly not visible |
21 | 21 |
// to COM components. If you need to access a type in this assembly from |
... | ... | |
52 | 52 |
// You can specify all the values or you can default the Build and Revision Numbers |
53 | 53 |
// by using the '*' as shown below: |
54 | 54 |
// [assembly: AssemblyVersion("1.0.*")] |
55 |
[assembly: AssemblyVersion("1.0.0.26001")] |
|
55 |
[assembly: AssemblyVersion("1.0.0.27000")] |
b/trunk/Pithos.Client.WPF/Services/StatusService.cs | ||
---|---|---|
6 | 6 |
|
7 | 7 |
|
8 | 8 |
using System; |
9 |
using System.ServiceModel.Description; |
|
10 | 9 |
using Caliburn.Micro; |
11 | 10 |
using System.ServiceModel; |
12 | 11 |
using System.ComponentModel.Composition; |
b/trunk/Pithos.Client.WPF/Shell/ShellViewModel.cs | ||
---|---|---|
1 | 1 |
using System.Collections.Concurrent; |
2 |
using System.ComponentModel; |
|
3 |
using System.ComponentModel.Composition; |
|
4 | 2 |
using System.Diagnostics; |
5 | 3 |
using System.Diagnostics.Contracts; |
6 | 4 |
using System.IO; |
... | ... | |
8 | 6 |
using System.Reflection; |
9 | 7 |
using System.Runtime.InteropServices; |
10 | 8 |
using System.ServiceModel; |
11 |
using System.ServiceModel.Description; |
|
12 | 9 |
using System.Threading.Tasks; |
13 | 10 |
using System.Windows; |
14 | 11 |
using Caliburn.Micro; |
15 | 12 |
using Hardcodet.Wpf.TaskbarNotification; |
16 | 13 |
using Pithos.Client.WPF.Configuration; |
17 | 14 |
using Pithos.Client.WPF.FileProperties; |
18 |
using Pithos.Client.WPF.Properties; |
|
19 | 15 |
using Pithos.Client.WPF.SelectiveSynch; |
20 | 16 |
using Pithos.Client.WPF.Services; |
21 | 17 |
using Pithos.Client.WPF.Shell; |
... | ... | |
24 | 20 |
using System; |
25 | 21 |
using System.Collections.Generic; |
26 | 22 |
using System.Linq; |
27 |
using System.Text; |
|
28 | 23 |
using Pithos.Network; |
29 | 24 |
using StatusService = Pithos.Client.WPF.Services.StatusService; |
30 | 25 |
|
... | ... | |
50 | 45 |
{ |
51 | 46 |
//The Status Checker provides the current synch state |
52 | 47 |
//TODO: Could we remove the status checker and use events in its place? |
53 |
private IStatusChecker _statusChecker; |
|
54 |
private IEventAggregator _events; |
|
48 |
private readonly IStatusChecker _statusChecker;
|
|
49 |
private readonly IEventAggregator _events;
|
|
55 | 50 |
|
56 | 51 |
public PithosSettings Settings { get; private set; } |
57 | 52 |
|
58 | 53 |
|
59 |
private ConcurrentDictionary<string, PithosMonitor> _monitors = new ConcurrentDictionary<string, PithosMonitor>(); |
|
54 |
private readonly ConcurrentDictionary<string, PithosMonitor> _monitors = new ConcurrentDictionary<string, PithosMonitor>();
|
|
60 | 55 |
///<summary> |
61 | 56 |
/// Dictionary of account monitors, keyed by account |
62 | 57 |
///</summary> |
... | ... | |
72 | 67 |
} |
73 | 68 |
|
74 | 69 |
|
75 |
///<summary>
|
|
76 |
/// The status service is used by Shell extensions to retrieve file status information
|
|
77 |
///</summary>
|
|
78 |
//TODO: CODE SMELL! This is the shell! While hosting in the shell makes executing start/stop commands easier, it is still a smell
|
|
79 |
private ServiceHost _statusService { get; set; }
|
|
70 |
///<summary>
|
|
71 |
/// The status service is used by Shell extensions to retrieve file status information
|
|
72 |
///</summary>
|
|
73 |
//TODO: CODE SMELL! This is the shell! While hosting in the shell makes executing start/stop commands easier, it is still a smell
|
|
74 |
private ServiceHost _statusService;
|
|
80 | 75 |
|
81 | 76 |
//Logging in the Pithos client is provided by log4net |
82 | 77 |
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger("Pithos"); |
... | ... | |
128 | 123 |
} |
129 | 124 |
|
130 | 125 |
|
131 |
private async Task StartMonitoring()
|
|
126 |
private async void StartMonitoring()
|
|
132 | 127 |
{ |
133 | 128 |
try |
134 | 129 |
{ |
... | ... | |
169 | 164 |
{ |
170 | 165 |
return Task.Factory.StartNew(() => |
171 | 166 |
{ |
172 |
PithosMonitor monitor = null;
|
|
167 |
PithosMonitor monitor; |
|
173 | 168 |
var accountName = account.AccountName; |
174 | 169 |
|
175 | 170 |
if (_monitors.TryGetValue(accountName, out monitor)) |
... | ... | |
199 | 194 |
//PithosMonitor uses MEF so we need to resolve it |
200 | 195 |
IoC.BuildUp(monitor); |
201 | 196 |
|
202 |
var appSettings = Properties.Settings.Default; |
|
203 |
monitor.AuthenticationUrl = account.ServerUrl; |
|
197 |
monitor.AuthenticationUrl = account.ServerUrl; |
|
204 | 198 |
|
205 | 199 |
_monitors[accountName] = monitor; |
206 | 200 |
|
... | ... | |
284 | 278 |
get { return _statusIcon; } |
285 | 279 |
set |
286 | 280 |
{ |
287 |
//_statusIcon = value; |
|
281 |
//TODO: Ensure all status icons use the Pithos logo |
|
282 |
_statusIcon = value; |
|
288 | 283 |
NotifyOfPropertyChange(() => StatusIcon); |
289 | 284 |
} |
290 | 285 |
} |
... | ... | |
359 | 354 |
{ |
360 | 355 |
if (String.IsNullOrWhiteSpace(filePath)) |
361 | 356 |
throw new ArgumentNullException("filePath"); |
362 |
if (!File.Exists(filePath)) |
|
357 |
if (!File.Exists(filePath) && !Directory.Exists(filePath))
|
|
363 | 358 |
throw new ArgumentException(String.Format("Non existent file {0}",filePath),"filePath"); |
364 | 359 |
Contract.EndContractBlock(); |
365 | 360 |
|
366 | 361 |
var pair=(from monitor in Monitors |
367 | 362 |
where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase) |
368 | 363 |
select monitor).FirstOrDefault(); |
369 |
var account = pair.Key; |
|
370 |
var accountMonitor = pair.Value; |
|
364 |
var accountMonitor = pair.Value; |
|
371 | 365 |
|
372 | 366 |
if (accountMonitor == null) |
373 | 367 |
return; |
... | ... | |
401 | 395 |
var pair=(from monitor in Monitors |
402 | 396 |
where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase) |
403 | 397 |
select monitor).FirstOrDefault(); |
404 |
var account = pair.Key; |
|
405 |
var accountMonitor = pair.Value; |
|
398 |
var accountMonitor = pair.Value; |
|
406 | 399 |
var info = accountMonitor.GetContainerInfo(filePath); |
407 | 400 |
|
408 | 401 |
|
... | ... | |
462 | 455 |
#endregion |
463 | 456 |
|
464 | 457 |
|
465 |
private Dictionary<PithosStatus, StatusInfo> iconNames = new List<StatusInfo>
|
|
458 |
private readonly Dictionary<PithosStatus, StatusInfo> _iconNames = new List<StatusInfo>
|
|
466 | 459 |
{ |
467 | 460 |
new StatusInfo(PithosStatus.InSynch, "All files up to date", "TrayInSynch"), |
468 | 461 |
new StatusInfo(PithosStatus.Syncing, "Syncing Files", "TraySynching"), |
... | ... | |
479 | 472 |
{ |
480 | 473 |
var pithosStatus = _statusChecker.GetPithosStatus(); |
481 | 474 |
|
482 |
if (iconNames.ContainsKey(pithosStatus)) |
|
475 |
if (_iconNames.ContainsKey(pithosStatus))
|
|
483 | 476 |
{ |
484 |
var info = iconNames[pithosStatus]; |
|
477 |
var info = _iconNames[pithosStatus];
|
|
485 | 478 |
StatusIcon = String.Format(@"../Images/{0}.ico", info.IconName); |
486 | 479 |
|
487 | 480 |
Assembly assembly = Assembly.GetExecutingAssembly(); |
... | ... | |
558 | 551 |
|
559 | 552 |
var credentials = await PithosAccount.RetrieveCredentials(Settings.PithosLoginUrl); |
560 | 553 |
|
561 |
var account = Settings.Accounts.FirstOrDefault(act => act.AccountName == credentials.UserName);
|
|
554 |
var account = Settings.Accounts.First(act => act.AccountName == credentials.UserName); |
|
562 | 555 |
account.ApiKey = credentials.Password; |
563 | 556 |
monitor.ApiKey = credentials.Password; |
564 | 557 |
Settings.Save(); |
... | ... | |
571 | 564 |
string message = String.Format("API Key retrieval for {0} failed", monitor.UserName); |
572 | 565 |
Log.Error(message, exc.InnerException); |
573 | 566 |
_events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error }); |
574 |
return; |
|
575 | 567 |
} |
576 | 568 |
catch (Exception exc) |
577 | 569 |
{ |
578 | 570 |
string message = String.Format("API Key retrieval for {0} failed", monitor.UserName); |
579 | 571 |
Log.Error(message, exc); |
580 | 572 |
_events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error }); |
581 |
return; |
|
582 |
|
|
583 | 573 |
} |
584 | 574 |
|
585 | 575 |
} |
... | ... | |
608 | 598 |
|
609 | 599 |
public void NotifyChange(string status, TraceLevel level=TraceLevel.Info) |
610 | 600 |
{ |
611 |
this.StatusMessage = status;
|
|
601 |
StatusMessage = status; |
|
612 | 602 |
|
613 | 603 |
_events.Publish(new Notification { Title = "Pithos", Message = status, Level = level }); |
614 | 604 |
} |
... | ... | |
616 | 606 |
public void NotifyChangedFile(string filePath) |
617 | 607 |
{ |
618 | 608 |
var entry = new FileEntry {FullPath=filePath}; |
619 |
IProducerConsumerCollection<FileEntry> files=this.RecentFiles;
|
|
609 |
IProducerConsumerCollection<FileEntry> files=RecentFiles; |
|
620 | 610 |
FileEntry popped; |
621 | 611 |
while (files.Count > 5) |
622 | 612 |
files.TryTake(out popped); |
... | ... | |
702 | 692 |
{ |
703 | 693 |
if (!Settings.ShowDesktopNotifications) |
704 | 694 |
return; |
705 |
BalloonIcon icon = BalloonIcon.None;
|
|
695 |
BalloonIcon icon; |
|
706 | 696 |
switch (notification.Level) |
707 | 697 |
{ |
708 | 698 |
case TraceLevel.Error: |
... | ... | |
722 | 712 |
|
723 | 713 |
if (Settings.ShowDesktopNotifications) |
724 | 714 |
{ |
725 |
var tv = (ShellView) this.GetView();
|
|
715 |
var tv = (ShellView) GetView(); |
|
726 | 716 |
tv.TaskbarView.ShowBalloonTip(notification.Title, notification.Message, icon); |
727 | 717 |
} |
728 | 718 |
} |
... | ... | |
737 | 727 |
Contract.EndContractBlock(); |
738 | 728 |
|
739 | 729 |
var fileName = message.FileName; |
740 |
|
|
730 |
//TODO: Display file properties for non-container folders |
|
741 | 731 |
if (File.Exists(fileName)) |
742 |
this.ShowFileProperties(fileName); |
|
732 |
//Retrieve the full name with exact casing. Pithos names are case sensitive |
|
733 |
ShowFileProperties(GetProperFilePathCapitalization(fileName)); |
|
743 | 734 |
else if (Directory.Exists(fileName)) |
744 |
this.ShowContainerProperties(fileName); |
|
735 |
//Retrieve the full name with exact casing. Pithos names are case sensitive |
|
736 |
{ |
|
737 |
var path = GetProperDirectoryCapitalization(fileName); |
|
738 |
if (IsContainer(path)) |
|
739 |
ShowContainerProperties(path); |
|
740 |
else |
|
741 |
ShowFileProperties(path); |
|
742 |
} |
|
745 | 743 |
} |
744 |
|
|
745 |
private bool IsContainer(string path) |
|
746 |
{ |
|
747 |
var matchingFolders = from account in _accounts |
|
748 |
from rootFolder in Directory.GetDirectories(account.AccountPath) |
|
749 |
where rootFolder.Equals(path, StringComparison.InvariantCultureIgnoreCase) |
|
750 |
select rootFolder; |
|
751 |
return matchingFolders.Any(); |
|
752 |
} |
|
753 |
|
|
754 |
static string GetProperDirectoryCapitalization(string fileName) |
|
755 |
{ |
|
756 |
var dirInfo = new DirectoryInfo(fileName); |
|
757 |
var parentDirInfo = dirInfo.Parent; |
|
758 |
if (null == parentDirInfo) |
|
759 |
return dirInfo.Name; |
|
760 |
return Path.Combine(GetProperDirectoryCapitalization(parentDirInfo.FullName), |
|
761 |
parentDirInfo.GetDirectories(dirInfo.Name)[0].Name); |
|
762 |
} |
|
763 |
|
|
764 |
static string GetProperFilePathCapitalization(string fileName) |
|
765 |
{ |
|
766 |
if (String.IsNullOrWhiteSpace(fileName)) |
|
767 |
throw new ArgumentNullException("fileName"); |
|
768 |
if (!Path.IsPathRooted(fileName)) |
|
769 |
throw new ArgumentException("fileName must be an absolute path","fileName"); |
|
770 |
Contract.EndContractBlock(); |
|
771 |
|
|
772 |
|
|
773 |
var fileInfo = new FileInfo(fileName); |
|
774 |
var dirInfo = fileInfo.Directory; |
|
775 |
|
|
776 |
//Directory will not be null for an absolute path |
|
777 |
Contract.Assume(dirInfo!=null); |
|
778 |
|
|
779 |
return Path.Combine(GetProperDirectoryCapitalization(dirInfo.FullName), |
|
780 |
dirInfo.GetFiles(fileInfo.Name)[0].Name); |
|
781 |
} |
|
746 | 782 |
} |
747 | 783 |
} |
b/trunk/Pithos.Core/Agents/CloudTransferAction.cs | ||
---|---|---|
21 | 21 |
{ |
22 | 22 |
public AccountInfo AccountInfo { get; set; } |
23 | 23 |
public CloudActionType Action { get; set; } |
24 |
public FileInfo LocalFile { get; set; } |
|
24 |
public FileSystemInfo LocalFile { get; set; }
|
|
25 | 25 |
public ObjectInfo CloudFile { get; set; } |
26 | 26 |
public FileState FileState { get; set; } |
27 | 27 |
public string Container { get; set; } |
... | ... | |
52 | 52 |
AccountInfo = accountInfo; |
53 | 53 |
} |
54 | 54 |
|
55 |
public CloudAction(AccountInfo accountInfo, CloudActionType action, FileInfo localFile, ObjectInfo cloudFile, FileState state, int blockSize, string algorithm) |
|
55 |
public CloudAction(AccountInfo accountInfo, CloudActionType action, FileSystemInfo localFile, ObjectInfo cloudFile, FileState state, int blockSize, string algorithm)
|
|
56 | 56 |
: this(accountInfo,action) |
57 | 57 |
{ |
58 | 58 |
LocalFile = localFile; |
... | ... | |
60 | 60 |
FileState = state; |
61 | 61 |
if (LocalFile != null) |
62 | 62 |
{ |
63 |
|
|
63 | 64 |
LocalHash = new Lazy<string>(() => LocalFile.CalculateHash(blockSize,algorithm), |
64 | 65 |
LazyThreadSafetyMode.ExecutionAndPublication); |
65 | 66 |
} |
... | ... | |
106 | 107 |
} |
107 | 108 |
public class CloudDeleteAction:CloudAction |
108 | 109 |
{ |
109 |
public CloudDeleteAction(AccountInfo accountInfo,FileInfo fileInfo, FileState fileState) |
|
110 |
public CloudDeleteAction(AccountInfo accountInfo,FileSystemInfo fileInfo, FileState fileState)
|
|
110 | 111 |
: this(accountInfo,new ObjectInfo(accountInfo.AccountPath,accountInfo.UserName,fileInfo),fileState) |
111 | 112 |
{ |
112 | 113 |
} |
... | ... | |
137 | 138 |
|
138 | 139 |
public class CloudUploadAction:CloudAction |
139 | 140 |
{ |
140 |
public CloudUploadAction(AccountInfo accountInfo, FileInfo fileInfo, FileState state, int blockSize, string algorithm) |
|
141 |
public CloudUploadAction(AccountInfo accountInfo, FileSystemInfo fileInfo, FileState state, int blockSize, string algorithm)
|
|
141 | 142 |
: base(accountInfo, CloudActionType.UploadUnconditional, fileInfo, |
142 | 143 |
new ObjectInfo(accountInfo.AccountPath, accountInfo.UserName,fileInfo), state, blockSize, algorithm) |
143 | 144 |
{ |
... | ... | |
155 | 156 |
{ |
156 | 157 |
public ObjectInfo OldCloudFile { get; set; } |
157 | 158 |
|
158 |
public FileInfo OldLocalFile { get; set; } |
|
159 |
public FileSystemInfo OldLocalFile { get; set; }
|
|
159 | 160 |
|
160 |
public CloudMoveAction(AccountInfo accountInfo, CloudActionType action, FileInfo oldFile, FileInfo newFile)
|
|
161 |
public CloudMoveAction(AccountInfo accountInfo, CloudActionType action, FileSystemInfo oldFile, FileSystemInfo newFile)
|
|
161 | 162 |
:base(accountInfo,action) |
162 | 163 |
{ |
163 | 164 |
LocalFile = newFile; |
b/trunk/Pithos.Core/Agents/FileAgent.cs | ||
---|---|---|
75 | 75 |
throw new ArgumentNullException("state"); |
76 | 76 |
Contract.EndContractBlock(); |
77 | 77 |
|
78 |
Debug.Assert(!Ignore(state.Path)); |
|
78 |
if (Ignore(state.Path)) |
|
79 |
return CompletedTask<object>.Default; |
|
79 | 80 |
|
80 | 81 |
var networkState = NetworkGate.GetNetworkState(state.Path); |
81 | 82 |
//Skip if the file is already being downloaded or uploaded and |
... | ... | |
192 | 193 |
|
193 | 194 |
private bool Ignore(string filePath) |
194 | 195 |
{ |
196 |
var pithosPath = Path.Combine(RootPath, "pithos"); |
|
197 |
if (pithosPath.Equals(filePath, StringComparison.InvariantCultureIgnoreCase)) |
|
198 |
return true; |
|
195 | 199 |
if (filePath.StartsWith(CachePath)) |
196 | 200 |
return true; |
197 | 201 |
if (_ignoreFiles.ContainsKey(filePath.ToLower())) |
... | ... | |
206 | 210 |
var filePath = e.FullPath; |
207 | 211 |
if (Ignore(filePath)) |
208 | 212 |
return; |
209 |
if (Directory.Exists(filePath)) |
|
210 |
return; |
|
213 |
/* if (Directory.Exists(filePath))
|
|
214 |
return; */
|
|
211 | 215 |
_agent.Post(new WorkflowState{AccountInfo=AccountInfo, Path = filePath, FileName = e.Name, TriggeringChange = e.ChangeType }); |
212 | 216 |
} |
213 | 217 |
|
... | ... | |
344 | 348 |
return false; |
345 | 349 |
} |
346 | 350 |
|
347 |
public FileInfo GetFileInfo(string relativePath) |
|
351 |
public FileSystemInfo GetFileInfo(string relativePath)
|
|
348 | 352 |
{ |
349 | 353 |
if (String.IsNullOrWhiteSpace(relativePath)) |
350 | 354 |
throw new ArgumentNullException("relativePath"); |
... | ... | |
354 | 358 |
Contract.EndContractBlock(); |
355 | 359 |
|
356 | 360 |
var absolutePath = Path.Combine(RootPath, relativePath); |
357 |
// Debug.Assert(File.Exists(absolutePath),String.Format("Path {0} doesn't exist",absolutePath)); |
|
358 | 361 |
|
359 |
return new FileInfo(absolutePath); |
|
362 |
if (Directory.Exists(absolutePath)) |
|
363 |
return new DirectoryInfo(absolutePath); |
|
364 |
else |
|
365 |
return new FileInfo(absolutePath); |
|
360 | 366 |
|
361 | 367 |
} |
362 | 368 |
|
363 | 369 |
public void Delete(string relativePath) |
364 | 370 |
{ |
365 |
var absolutePath = Path.Combine(RootPath, relativePath); |
|
371 |
var absolutePath = Path.Combine(RootPath, relativePath).ToLower();
|
|
366 | 372 |
if (File.Exists(absolutePath)) |
367 |
{ |
|
368 |
File.Delete(absolutePath); |
|
369 |
_ignoreFiles[absolutePath.ToLower()] = absolutePath.ToLower(); |
|
373 |
{ |
|
374 |
try |
|
375 |
{ |
|
376 |
File.Delete(absolutePath); |
|
377 |
} |
|
378 |
//The file may have been deleted by another thread. Just ignore the relevant exception |
|
379 |
catch (FileNotFoundException) { } |
|
380 |
} |
|
381 |
else if (Directory.Exists(absolutePath)) |
|
382 |
{ |
|
383 |
try |
|
384 |
{ |
|
385 |
Directory.Delete(absolutePath, true); |
|
386 |
} |
|
387 |
//The directory may have been deleted by another thread. Just ignore the relevant exception |
|
388 |
catch (DirectoryNotFoundException){} |
|
370 | 389 |
} |
390 |
|
|
391 |
//_ignoreFiles[absolutePath] = absolutePath; |
|
371 | 392 |
StatusKeeper.ClearFileStatus(absolutePath); |
372 | 393 |
} |
373 | 394 |
} |
b/trunk/Pithos.Core/Agents/FileInfoExtensions.cs | ||
---|---|---|
24 | 24 |
} |
25 | 25 |
} |
26 | 26 |
|
27 |
public static string CalculateHash(this FileInfo info,int blockSize,string algorithm) |
|
27 |
public static string CalculateHash(this FileSystemInfo info,int blockSize,string algorithm)
|
|
28 | 28 |
{ |
29 | 29 |
if (info==null) |
30 | 30 |
throw new ArgumentNullException("info"); |
... | ... | |
36 | 36 |
throw new ArgumentNullException("algorithm"); |
37 | 37 |
Contract.EndContractBlock(); |
38 | 38 |
|
39 |
if (info.Length <= blockSize) |
|
39 |
//The hash for directories is an empty string |
|
40 |
if (info is DirectoryInfo) |
|
41 |
return String.Empty; |
|
42 |
|
|
43 |
var fileInfo = (FileInfo)info; |
|
44 |
if (fileInfo.Length <= blockSize) |
|
40 | 45 |
return Signature.CalculateMD5(info.FullName); |
41 | 46 |
else |
42 | 47 |
return Signature.CalculateTreeHash(info.FullName, blockSize, algorithm).TopHash.ToHashString(); |
43 | 48 |
|
44 | 49 |
} |
45 | 50 |
|
51 |
|
|
52 |
|
|
46 | 53 |
} |
47 | 54 |
} |
b/trunk/Pithos.Core/Agents/NetworkAgent.cs | ||
---|---|---|
1 | 1 |
using System; |
2 |
using System.Collections.Concurrent; |
|
2 | 3 |
using System.Collections.Generic; |
3 | 4 |
using System.ComponentModel.Composition; |
4 | 5 |
using System.Diagnostics; |
... | ... | |
26 | 27 |
|
27 | 28 |
private static readonly ILog Log = LogManager.GetLogger("NetworkAgent"); |
28 | 29 |
|
29 |
private readonly List<AccountInfo> _accounts=new List<AccountInfo>();
|
|
30 |
private readonly ConcurrentBag<AccountInfo> _accounts = new ConcurrentBag<AccountInfo>();
|
|
30 | 31 |
|
31 | 32 |
public void Start() |
32 | 33 |
{ |
... | ... | |
81 | 82 |
RenameCloudFile(accountInfo, moveAction); |
82 | 83 |
break; |
83 | 84 |
case CloudActionType.MustSynch: |
84 |
|
|
85 |
if (!File.Exists(downloadPath)) |
|
85 |
if (!File.Exists(downloadPath) && !Directory.Exists(downloadPath)) |
|
86 | 86 |
{ |
87 | 87 |
await DownloadCloudFile(accountInfo, cloudFile, downloadPath); |
88 | 88 |
} |
... | ... | |
221 | 221 |
Contract.EndContractBlock(); |
222 | 222 |
|
223 | 223 |
//If the action targets a local file, add a treehash calculation |
224 |
if (cloudAction.LocalFile != null) |
|
224 |
if (cloudAction.LocalFile as FileInfo != null)
|
|
225 | 225 |
{ |
226 | 226 |
var accountInfo = cloudAction.AccountInfo; |
227 |
if (!Directory.Exists(cloudAction.LocalFile.FullName)) |
|
227 |
var localFile = (FileInfo) cloudAction.LocalFile; |
|
228 |
if (localFile.Length > accountInfo.BlockSize) |
|
229 |
cloudAction.TopHash = |
|
230 |
new Lazy<string>(() => Signature.CalculateTreeHashAsync(localFile, |
|
231 |
accountInfo.BlockSize, |
|
232 |
accountInfo.BlockHash).Result |
|
233 |
.TopHash.ToHashString()); |
|
234 |
else |
|
228 | 235 |
{ |
229 |
if (cloudAction.LocalFile.Length > accountInfo.BlockSize) |
|
230 |
cloudAction.TopHash = |
|
231 |
new Lazy<string>(() => Signature.CalculateTreeHashAsync(cloudAction.LocalFile, |
|
232 |
accountInfo.BlockSize, |
|
233 |
accountInfo.BlockHash).Result |
|
234 |
.TopHash.ToHashString()); |
|
235 |
else |
|
236 |
{ |
|
237 |
cloudAction.TopHash = new Lazy<string>(() => cloudAction.LocalHash.Value); |
|
238 |
} |
|
236 |
cloudAction.TopHash = new Lazy<string>(() => cloudAction.LocalHash.Value); |
|
239 | 237 |
} |
240 |
|
|
238 |
} |
|
239 |
else |
|
240 |
{ |
|
241 |
//The hash for a directory is the empty string |
|
242 |
cloudAction.TopHash = new Lazy<string>(() => String.Empty); |
|
241 | 243 |
} |
242 | 244 |
_agent.Post(cloudAction); |
243 | 245 |
} |
... | ... | |
274 | 276 |
var tasks = from accountInfo in _accounts |
275 | 277 |
select ProcessAccountFiles(accountInfo, since); |
276 | 278 |
|
277 |
await TaskEx.WhenAll(tasks); |
|
279 |
await TaskEx.WhenAll(tasks.ToList());
|
|
278 | 280 |
|
279 | 281 |
ProcessRemoteFiles(nextSince); |
280 | 282 |
} |
... | ... | |
377 | 379 |
} |
378 | 380 |
} |
379 | 381 |
|
380 |
private static void CreateContainerFolders(AccountInfo accountInfo, IList<ContainerInfo> containers)
|
|
382 |
private static void CreateContainerFolders(AccountInfo accountInfo, IEnumerable<ContainerInfo> containers)
|
|
381 | 383 |
{ |
382 | 384 |
var containerPaths = from container in containers |
383 | 385 |
let containerPath = Path.Combine(accountInfo.AccountPath, container.Name) |
... | ... | |
407 | 409 |
|
408 | 410 |
if (fileAgent.Exists(relativePath)) |
409 | 411 |
{ |
412 |
//If a directory object already exists, we don't need to perform any other action |
|
410 | 413 |
var localFile = fileAgent.GetFileInfo(relativePath); |
414 |
if (objectInfo.Content_Type == @"application/directory" && localFile is DirectoryInfo) |
|
415 |
continue; |
|
411 | 416 |
var state = FileState.FindByFilePath(localFile.FullName); |
412 | 417 |
//Common files should be checked on a per-case basis to detect differences, which is newer |
413 | 418 |
|
... | ... | |
756 | 761 |
var cloudFile = action.CloudFile; |
757 | 762 |
var account = cloudFile.Account ?? accountInfo.UserName; |
758 | 763 |
|
759 |
var client = new CloudFilesClient(accountInfo); |
|
764 |
var client = new CloudFilesClient(accountInfo);
|
|
760 | 765 |
//Even if GetObjectInfo times out, we can proceed with the upload |
761 | 766 |
var info = client.GetObjectInfo(account, cloudFile.Container, cloudFile.Name); |
762 |
var cloudHash = info.Hash.ToLower(); |
|
763 | 767 |
|
764 |
var hash = action.LocalHash.Value; |
|
765 |
var topHash = action.TopHash.Value; |
|
768 |
//If this is a read-only file, do not upload changes |
|
769 |
if (info.AllowedTo == "read") |
|
770 |
return; |
|
766 | 771 |
|
767 |
//If the file hashes match, abort the upload |
|
768 |
if (hash == cloudHash || topHash == cloudHash) |
|
772 |
//WRONG: If this is a directory, there is no hash to check. ???? |
|
773 |
//TODO: Check how a directory hash is calculated |
|
774 |
if (fileInfo is DirectoryInfo) |
|
769 | 775 |
{ |
770 |
//but store any metadata changes
|
|
771 |
StatusKeeper.StoreInfo(fullFileName, info);
|
|
772 |
Log.InfoFormat("Skip upload of {0}, hashes match", fullFileName);
|
|
773 |
return;
|
|
776 |
//If the directory doesn't exist the Hash property will be empty
|
|
777 |
if (String.IsNullOrWhiteSpace(info.Hash))
|
|
778 |
//Go on and create the directory
|
|
779 |
client.PutObject(account, cloudFile.Container, cloudFile.Name, fileInfo.FullName, String.Empty, "application/directory");
|
|
774 | 780 |
} |
781 |
else |
|
782 |
{ |
|
775 | 783 |
|
776 |
if (info.AllowedTo == "read") |
|
777 |
return; |
|
784 |
var cloudHash = info.Hash.ToLower(); |
|
778 | 785 |
|
779 |
//Mark the file as modified while we upload it |
|
780 |
StatusKeeper.SetFileOverlayStatus(fullFileName, FileOverlayStatus.Modified); |
|
781 |
//And then upload it |
|
786 |
var hash = action.LocalHash.Value; |
|
787 |
var topHash = action.TopHash.Value; |
|
788 |
|
|
789 |
//If the file hashes match, abort the upload |
|
790 |
if (hash == cloudHash || topHash == cloudHash) |
|
791 |
{ |
|
792 |
//but store any metadata changes |
|
793 |
StatusKeeper.StoreInfo(fullFileName, info); |
|
794 |
Log.InfoFormat("Skip upload of {0}, hashes match", fullFileName); |
|
795 |
return; |
|
796 |
} |
|
782 | 797 |
|
783 |
//Upload even small files using the Hashmap. The server may already containt |
|
784 |
//the relevant folder |
|
785 | 798 |
|
786 |
//First, calculate the tree hash
|
|
787 |
var treeHash = await Signature.CalculateTreeHashAsync(fileInfo.FullName, accountInfo.BlockSize,
|
|
788 |
accountInfo.BlockHash);
|
|
799 |
//Mark the file as modified while we upload it
|
|
800 |
StatusKeeper.SetFileOverlayStatus(fullFileName, FileOverlayStatus.Modified);
|
|
801 |
//And then upload it
|
|
789 | 802 |
|
790 |
await UploadWithHashMap(accountInfo, cloudFile, fileInfo, cloudFile.Name, treeHash); |
|
803 |
//Upload even small files using the Hashmap. The server may already contain |
|
804 |
//the relevant block |
|
791 | 805 |
|
806 |
//First, calculate the tree hash |
|
807 |
var treeHash = await Signature.CalculateTreeHashAsync(fileInfo.FullName, accountInfo.BlockSize, |
|
808 |
accountInfo.BlockHash); |
|
809 |
|
|
810 |
await UploadWithHashMap(accountInfo, cloudFile, fileInfo as FileInfo, cloudFile.Name, treeHash); |
|
811 |
} |
|
792 | 812 |
//If everything succeeds, change the file and overlay status to normal |
793 | 813 |
StatusKeeper.SetFileState(fullFileName, FileStatus.Unchanged, FileOverlayStatus.Normal); |
794 | 814 |
} |
b/trunk/Pithos.Core/Agents/WorkflowAgent.cs | ||
---|---|---|
62 | 62 |
} |
63 | 63 |
string path = state.Path.ToLower(); |
64 | 64 |
|
65 |
|
|
66 |
|
|
67 |
FileSystemInfo info = Directory.Exists(path) ? (FileSystemInfo) new DirectoryInfo(path) : new FileInfo(path); |
|
68 |
|
|
65 | 69 |
//Bypass deleted files, unless the status is Deleted |
66 |
if (!File.Exists(path) && state.Status != FileStatus.Deleted)
|
|
70 |
if (!info.Exists && state.Status != FileStatus.Deleted)
|
|
67 | 71 |
{ |
68 | 72 |
state.Skip = true; |
69 | 73 |
this.StatusKeeper.ClearFileStatus(path); |
... | ... | |
74 | 78 |
} |
75 | 79 |
|
76 | 80 |
var fileState = FileState.FindByFilePath(path); |
77 |
var info = new FileInfo(path); |
|
78 | 81 |
|
79 | 82 |
|
80 | 83 |
switch (state.Status) |
... | ... | |
88 | 91 |
NetworkAgent.Post(new CloudDeleteAction(accountInfo, info, fileState)); |
89 | 92 |
break; |
90 | 93 |
case FileStatus.Renamed: |
94 |
FileSystemInfo oldInfo = Directory.Exists(state.OldPath) ? (FileSystemInfo)new DirectoryInfo(state.OldPath) : new FileInfo(state.OldPath); |
|
95 |
FileSystemInfo newInfo = Directory.Exists(state.Path) ? (FileSystemInfo)new DirectoryInfo(state.Path) : new FileInfo(state.Path); |
|
91 | 96 |
NetworkAgent.Post(new CloudMoveAction(accountInfo, CloudActionType.RenameCloud, |
92 |
new FileInfo(state.OldPath),
|
|
93 |
new FileInfo(state.Path)));
|
|
97 |
oldInfo,
|
|
98 |
newInfo));
|
|
94 | 99 |
break; |
95 | 100 |
} |
96 | 101 |
|
... | ... | |
131 | 136 |
var pendingStates = new List<WorkflowState>(); |
132 | 137 |
foreach (var state in pendingEntries) |
133 | 138 |
{ |
134 |
if (!Directory.Exists(state.FilePath)) |
|
135 | 139 |
pendingStates.Add(new WorkflowState(account, state)); |
136 | 140 |
} |
137 | 141 |
if (Log.IsDebugEnabled) |
... | ... | |
140 | 144 |
|
141 | 145 |
foreach (var entry in pendingStates) |
142 | 146 |
{ |
143 |
//Remove invalid state |
|
144 |
if (Directory.Exists(entry.Path)) |
|
145 |
{ |
|
146 |
Debug.Assert(false, "State has path instead of file", entry.Path); |
|
147 |
StatusKeeper.ClearFileStatus(entry.Path); |
|
148 |
return; |
|
149 |
} |
|
150 |
else |
|
151 |
Post(entry); |
|
147 |
Post(entry); |
|
152 | 148 |
} |
153 | 149 |
} |
154 | 150 |
} |
... | ... | |
161 | 157 |
Log.DebugFormat("Posted {0} {1} {2}", workflowState.Path, workflowState.Status, workflowState.TriggeringChange); |
162 | 158 |
|
163 | 159 |
//Remove invalid state |
164 |
Debug.Assert(!Directory.Exists(workflowState.Path), "State has path instead of file", workflowState.Path); |
|
160 |
//For now, ignore paths |
|
161 |
/* if (Directory.Exists(workflowState.Path)) |
|
162 |
return;*/ |
|
163 |
//TODO: Need to handle folder renames |
|
165 | 164 |
|
166 | 165 |
Debug.Assert(workflowState.Path.StartsWith(workflowState.AccountInfo.AccountPath, StringComparison.InvariantCultureIgnoreCase), "File from wrong account posted"); |
167 | 166 |
|
b/trunk/Pithos.Interfaces/FileInfoExtensions.cs | ||
---|---|---|
12 | 12 |
{ |
13 | 13 |
public static class FileInfoExtensions |
14 | 14 |
{ |
15 |
public static string AsRelativeTo(this FileInfo fileInfo,string path ) |
|
15 |
public static string AsRelativeTo(this FileSystemInfo fileInfo,string path )
|
|
16 | 16 |
{ |
17 | 17 |
if (String.IsNullOrWhiteSpace(path)) |
18 | 18 |
throw new ArgumentNullException("path"); |
... | ... | |
33 | 33 |
return relativePath; |
34 | 34 |
} |
35 | 35 |
|
36 |
public static string AsRelativeUrlTo(this FileInfo fileInfo,string path ) |
|
36 |
public static string AsRelativeUrlTo(this FileSystemInfo fileInfo,string path )
|
|
37 | 37 |
{ |
38 | 38 |
if (String.IsNullOrWhiteSpace(path)) |
39 | 39 |
throw new ArgumentNullException("path"); |
b/trunk/Pithos.Interfaces/ObjectInfo.cs | ||
---|---|---|
114 | 114 |
public ObjectInfo() |
115 | 115 |
{} |
116 | 116 |
|
117 |
public ObjectInfo(string accountPath,string accountName,FileInfo fileInfo) |
|
117 |
public ObjectInfo(string accountPath,string accountName,FileSystemInfo fileInfo)
|
|
118 | 118 |
{ |
119 | 119 |
if (String.IsNullOrWhiteSpace(accountPath)) |
120 | 120 |
throw new ArgumentNullException("accountPath"); |
b/trunk/Pithos.Network/CloudFilesClient.cs | ||
---|---|---|
219 | 219 |
|
220 | 220 |
private string GetAccountUrl(string account) |
221 | 221 |
{ |
222 |
return new Uri(this.RootAddressUri, new Uri(account,UriKind.Relative)).AbsoluteUri;
|
|
222 |
return new Uri(RootAddressUri, new Uri(account,UriKind.Relative)).AbsoluteUri; |
|
223 | 223 |
} |
224 | 224 |
|
225 | 225 |
public IList<ShareAccountInfo> ListSharingAccounts(DateTime? since=null) |
226 | 226 |
{ |
227 |
using (log4net.ThreadContext.Stacks["Share"].Push("List Accounts"))
|
|
227 |
using (ThreadContext.Stacks["Share"].Push("List Accounts")) |
|
228 | 228 |
{ |
229 | 229 |
if (Log.IsDebugEnabled) Log.DebugFormat("START"); |
230 | 230 |
|
... | ... | |
258 | 258 |
public IList<ObjectInfo> ListSharedObjects(DateTime? since = null) |
259 | 259 |
{ |
260 | 260 |
|
261 |
using (log4net.ThreadContext.Stacks["Share"].Push("List Objects"))
|
|
261 |
using (ThreadContext.Stacks["Share"].Push("List Objects")) |
|
262 | 262 |
{ |
263 | 263 |
if (Log.IsDebugEnabled) Log.DebugFormat("START"); |
264 | 264 |
|
... | ... | |
269 | 269 |
var containers = ListContainers(account.name); |
270 | 270 |
foreach (var container in containers) |
271 | 271 |
{ |
272 |
var containerObjects = ListObjects(account.name, container.Name, null);
|
|
272 |
var containerObjects = ListObjects(account.name, container.Name); |
|
273 | 273 |
objects.AddRange(containerObjects); |
274 | 274 |
} |
275 | 275 |
} |
... | ... | |
288 | 288 |
throw new ArgumentNullException("target"); |
289 | 289 |
Contract.EndContractBlock(); |
290 | 290 |
|
291 |
using (log4net.ThreadContext.Stacks["Share"].Push("Share Object"))
|
|
291 |
using (ThreadContext.Stacks["Share"].Push("Share Object")) |
|
292 | 292 |
{ |
293 | 293 |
if (Log.IsDebugEnabled) Log.DebugFormat("START"); |
294 | 294 |
|
... | ... | |
306 | 306 |
client.Headers.Add(headerTag, tag.Value); |
307 | 307 |
} |
308 | 308 |
|
309 |
var content = client.DownloadStringWithRetry(target.Container, 3);
|
|
309 |
client.DownloadStringWithRetry(target.Container, 3); |
|
310 | 310 |
|
311 | 311 |
|
312 | 312 |
client.AssertStatusOK("SetTags failed"); |
... | ... | |
340 | 340 |
throw new ArgumentNullException("shareTo"); |
341 | 341 |
Contract.EndContractBlock(); |
342 | 342 |
|
343 |
using (log4net.ThreadContext.Stacks["Share"].Push("Share Object"))
|
|
343 |
using (ThreadContext.Stacks["Share"].Push("Share Object")) |
|
344 | 344 |
{ |
345 | 345 |
if (Log.IsDebugEnabled) Log.DebugFormat("START"); |
346 | 346 |
|
... | ... | |
382 | 382 |
throw new ArgumentNullException("accountInfo"); |
383 | 383 |
Contract.EndContractBlock(); |
384 | 384 |
|
385 |
using (log4net.ThreadContext.Stacks["Account"].Push("GetPolicies"))
|
|
385 |
using (ThreadContext.Stacks["Account"].Push("GetPolicies")) |
|
386 | 386 |
{ |
387 | 387 |
if (Log.IsDebugEnabled) Log.DebugFormat("START"); |
388 | 388 |
|
... | ... | |
417 | 417 |
throw new ArgumentNullException("objectInfo"); |
418 | 418 |
Contract.EndContractBlock(); |
419 | 419 |
|
420 |
using (log4net.ThreadContext.Stacks["Objects"].Push("UpdateMetadata"))
|
|
420 |
using (ThreadContext.Stacks["Objects"].Push("UpdateMetadata")) |
|
421 | 421 |
{ |
422 | 422 |
if (Log.IsDebugEnabled) Log.DebugFormat("START"); |
423 | 423 |
|
... | ... | |
452 | 452 |
var uriBuilder = client.GetAddressBuilder(objectInfo.Container, objectInfo.Name); |
453 | 453 |
var uri = uriBuilder.Uri; |
454 | 454 |
|
455 |
var content = client.UploadValues(uri,new NameValueCollection());
|
|
455 |
client.UploadValues(uri,new NameValueCollection()); |
|
456 | 456 |
|
457 | 457 |
|
458 | 458 |
client.AssertStatusOK("UpdateMetadata failed"); |
... | ... | |
475 | 475 |
throw new ArgumentNullException("containerInfo"); |
476 | 476 |
Contract.EndContractBlock(); |
477 | 477 |
|
478 |
using (log4net.ThreadContext.Stacks["Containers"].Push("UpdateMetadata"))
|
|
478 |
using (ThreadContext.Stacks["Containers"].Push("UpdateMetadata")) |
|
479 | 479 |
{ |
480 | 480 |
if (Log.IsDebugEnabled) Log.DebugFormat("START"); |
481 | 481 |
|
... | ... | |
507 | 507 |
var uriBuilder = client.GetAddressBuilder(containerInfo.Name,""); |
508 | 508 |
var uri = uriBuilder.Uri; |
509 | 509 |
|
510 |
var content = client.UploadValues(uri,new NameValueCollection());
|
|
510 |
client.UploadValues(uri,new NameValueCollection()); |
|
511 | 511 |
|
512 | 512 |
|
513 | 513 |
client.AssertStatusOK("UpdateMetadata failed"); |
... | ... | |
531 | 531 |
throw new ArgumentNullException("container"); |
532 | 532 |
Contract.EndContractBlock(); |
533 | 533 |
|
534 |
using (log4net.ThreadContext.Stacks["Objects"].Push("List"))
|
|
534 |
using (ThreadContext.Stacks["Objects"].Push("List")) |
|
535 | 535 |
{ |
536 | 536 |
if (Log.IsDebugEnabled) Log.DebugFormat("START"); |
537 | 537 |
|
... | ... | |
574 | 574 |
throw new ArgumentNullException("folder"); |
575 | 575 |
Contract.EndContractBlock(); |
576 | 576 |
|
577 |
using (log4net.ThreadContext.Stacks["Objects"].Push("List"))
|
|
577 |
using (ThreadContext.Stacks["Objects"].Push("List")) |
|
578 | 578 |
{ |
579 | 579 |
if (Log.IsDebugEnabled) Log.DebugFormat("START"); |
580 | 580 |
|
... | ... | |
605 | 605 |
throw new ArgumentNullException("container", "The container property can't be empty"); |
606 | 606 |
Contract.EndContractBlock(); |
607 | 607 |
|
608 |
using (log4net.ThreadContext.Stacks["Containters"].Push("Exists"))
|
|
608 |
using (ThreadContext.Stacks["Containters"].Push("Exists")) |
|
609 | 609 |
{ |
610 | 610 |
if (Log.IsDebugEnabled) Log.DebugFormat("START"); |
611 | 611 |
|
... | ... | |
676 | 676 |
throw new ArgumentNullException("objectName", "The objectName property can't be empty"); |
677 | 677 |
Contract.EndContractBlock(); |
678 | 678 |
|
679 |
using (log4net.ThreadContext.Stacks["Objects"].Push("GetObjectInfo"))
|
|
679 |
using (ThreadContext.Stacks["Objects"].Push("GetObjectInfo")) |
|
680 | 680 |
{ |
681 | 681 |
|
682 | 682 |
using (var client = new RestClient(_baseClient)) |
... | ... | |
735 | 735 |
} |
736 | 736 |
catch (RetryException) |
737 | 737 |
{ |
738 |
Log.WarnFormat("[RETRY FAIL] GetObjectInfo for {0} failed."); |
|
738 |
Log.WarnFormat("[RETRY FAIL] GetObjectInfo for {0} failed.",objectName);
|
|
739 | 739 |
return ObjectInfo.Empty; |
740 | 740 |
} |
741 | 741 |
catch (WebException e) |
... | ... | |
1001 | 1001 |
|
1002 | 1002 |
return hashes; |
1003 | 1003 |
} |
1004 |
} |
|
1005 |
else |
|
1006 |
//Any other status code is unexpected and the exception should be rethrown |
|
1007 |
throw ex; |
|
1004 |
} |
|
1005 |
//Any other status code is unexpected and the exception should be rethrown |
|
1006 |
throw ex; |
|
1008 | 1007 |
|
1009 | 1008 |
} |
1010 | 1009 |
//Any other status code is unexpected but there was no exception. We can probably continue processing |
1011 |
else |
|
1012 |
{ |
|
1013 |
Log.WarnFormat("Unexcpected status code when putting map: {0} - {1}",client.StatusCode,client.StatusDescription); |
|
1014 |
} |
|
1010 |
Log.WarnFormat("Unexcpected status code when putting map: {0} - {1}",client.StatusCode,client.StatusDescription); |
|
1011 |
|
|
1015 | 1012 |
return empty; |
1016 | 1013 |
}); |
1017 | 1014 |
|
... | ... | |
1051 | 1048 |
} |
1052 | 1049 |
|
1053 | 1050 |
|
1054 |
public Task PostBlock(string account, string container, byte[] block, int offset, int count) |
|
1051 |
public async Task PostBlock(string account, string container, byte[] block, int offset, int count)
|
|
1055 | 1052 |
{ |
1056 | 1053 |
if (String.IsNullOrWhiteSpace(container)) |
1057 | 1054 |
throw new ArgumentNullException("container"); |
... | ... | |
1067 | 1064 |
throw new InvalidOperationException("Invalid Storage Url"); |
1068 | 1065 |
Contract.EndContractBlock(); |
1069 | 1066 |
|
1070 |
|
|
1071 |
//Don't use a timeout because putting the hashmap may be a long process |
|
1072 |
var client = new RestClient(_baseClient) { Timeout = 0 }; |
|
1073 |
if (!String.IsNullOrWhiteSpace(account)) |
|
1074 |
client.BaseAddress = GetAccountUrl(account); |
|
1075 | 1067 |
|
1076 |
var builder = client.GetAddressBuilder(container, ""); |
|
1077 |
//We are doing an update |
|
1078 |
builder.Query = "update"; |
|
1079 |
var uri = builder.Uri; |
|
1068 |
try |
|
1069 |
{ |
|
1080 | 1070 |
|
1081 |
client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream"; |
|
1071 |
//Don't use a timeout because putting the hashmap may be a long process |
|
1072 |
using (var client = new RestClient(_baseClient) { Timeout = 0 }) |
|
1073 |
{ |
|
1074 |
if (!String.IsNullOrWhiteSpace(account)) |
|
1075 |
client.BaseAddress = GetAccountUrl(account); |
|
1076 |
|
|
1077 |
var builder = client.GetAddressBuilder(container, ""); |
|
1078 |
//We are doing an update |
|
1079 |
builder.Query = "update"; |
|
1080 |
var uri = builder.Uri; |
|
1082 | 1081 |
|
1083 |
Log.InfoFormat("[BLOCK POST] START");
|
|
1082 |
client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";
|
|
1084 | 1083 |
|
1085 |
client.UploadProgressChanged += (sender, args) => |
|
1086 |
Log.InfoFormat("[BLOCK POST PROGRESS] {0}% {1} of {2}", |
|
1087 |
args.ProgressPercentage, args.BytesSent, |
|
1088 |
args.TotalBytesToSend); |
|
1089 |
client.UploadFileCompleted += (sender, args) => |
|
1090 |
Log.InfoFormat("[BLOCK POST PROGRESS] Completed "); |
|
1084 |
Log.InfoFormat("[BLOCK POST] START"); |
|
1091 | 1085 |
|
1092 |
var buffer = new byte[count]; |
|
1093 |
Buffer.BlockCopy(block,offset,buffer,0,count); |
|
1094 |
//Send the block |
|
1095 |
var uploadTask = client.UploadDataTask(uri, "POST", buffer) |
|
1096 |
.ContinueWith(upload => |
|
1097 |
{ |
|
1098 |
client.Dispose(); |
|
1086 |
client.UploadProgressChanged += (sender, args) => |
|
1087 |
Log.InfoFormat("[BLOCK POST PROGRESS] {0}% {1} of {2}", |
|
1088 |
args.ProgressPercentage, args.BytesSent, |
|
1089 |
args.TotalBytesToSend); |
|
1090 |
client.UploadFileCompleted += (sender, args) => |
|
1091 |
Log.InfoFormat("[BLOCK POST PROGRESS] Completed "); |
|
1099 | 1092 |
|
1100 |
if (upload.IsFaulted)
|
|
1101 |
{
|
|
1102 |
var exception = upload.Exception.InnerException;
|
|
1103 |
Log.ErrorFormat("[BLOCK POST] FAIL with \r{0}", exception);
|
|
1104 |
throw exception;
|
|
1093 |
var buffer = new byte[count];
|
|
1094 |
Buffer.BlockCopy(block, offset, buffer, 0, count);
|
|
1095 |
//Send the block
|
|
1096 |
await client.UploadDataTask(uri, "POST", buffer);
|
|
1097 |
Log.InfoFormat("[BLOCK POST] END");
|
|
1105 | 1098 |
} |
1106 |
|
|
1107 |
Log.InfoFormat("[BLOCK POST] END"); |
|
1108 |
}); |
|
1109 |
return uploadTask; |
|
1099 |
} |
|
1100 |
catch (Exception exc) |
|
1101 |
{ |
|
1102 |
Log.ErrorFormat("[BLOCK POST] FAIL with \r{0}", exc); |
|
1103 |
throw; |
|
1104 |
} |
|
1110 | 1105 |
} |
1111 | 1106 |
|
1112 | 1107 |
|
... | ... | |
1167 | 1162 |
/// <param name="fileName"></param> |
1168 | 1163 |
/// <param name="hash">Optional hash value for the file. If no hash is provided, the method calculates a new hash</param> |
1169 | 1164 |
/// <remarks>>This method should have no timeout or a very long one</remarks> |
1170 |
public Task PutObject(string account, string container, string objectName, string fileName, string hash = null)
|
|
1165 |
public async Task PutObject(string account, string container, string objectName, string fileName, string hash = null, string contentType = "application/octet-stream")
|
|
1171 | 1166 |
{ |
1172 | 1167 |
if (String.IsNullOrWhiteSpace(container)) |
1173 | 1168 |
throw new ArgumentNullException("container", "The container property can't be empty"); |
... | ... | |
1175 | 1170 |
throw new ArgumentNullException("objectName", "The objectName property can't be empty"); |
1176 | 1171 |
if (String.IsNullOrWhiteSpace(fileName)) |
1177 | 1172 |
throw new ArgumentNullException("fileName", "The fileName property can't be empty"); |
1178 |
if (!File.Exists(fileName)) |
|
1179 |
throw new FileNotFoundException("The file does not exist",fileName); |
|
1173 |
/* |
|
1174 |
if (!File.Exists(fileName) && !Directory.Exists(fileName)) |
|
1175 |
throw new FileNotFoundException("The file or directory does not exist",fileName); |
|
1176 |
*/ |
|
1180 | 1177 |
Contract.EndContractBlock(); |
1181 | 1178 |
|
1182 | 1179 |
try |
1183 | 1180 |
{ |
1184 | 1181 |
|
1185 |
var client = new RestClient(_baseClient){Timeout=0}; |
|
1186 |
if (!String.IsNullOrWhiteSpace(account)) |
|
1187 |
client.BaseAddress = GetAccountUrl(account); |
|
1188 |
|
|
1189 |
var builder = client.GetAddressBuilder(container, objectName); |
|
1190 |
var uri = builder.Uri; |
|
1191 |
|
|
1192 |
string etag = hash ?? CalculateHash(fileName); |
|
1193 |
|
|
1194 |
client.Headers.Add("Content-Type", "application/octet-stream"); |
|
1195 |
client.Headers.Add("ETag", etag); |
|
1196 |
|
|
1197 |
|
|
1198 |
Log.InfoFormat("[PUT] START {0}", objectName); |
|
1199 |
client.UploadProgressChanged += (sender, args) => |
|
1182 |
using (var client = new RestClient(_baseClient) { Timeout = 0 }) |
|
1200 | 1183 |
{ |
1201 |
using (log4net.ThreadContext.Stacks["PUT"].Push("Progress")) |
|
1202 |
{ |
|
1203 |
Log.InfoFormat("{0} {1}% {2} of {3}", fileName, args.ProgressPercentage, |
|
1204 |
args.BytesSent, args.TotalBytesToSend); |
|
1205 |
} |
|
1206 |
}; |
|
1184 |
if (!String.IsNullOrWhiteSpace(account)) |
|
1185 |
client.BaseAddress = GetAccountUrl(account); |
|
1207 | 1186 |
|
1208 |
client.UploadFileCompleted += (sender, args) => |
|
1209 |
{ |
|
1210 |
using (log4net.ThreadContext.Stacks["PUT"].Push("Progress")) |
|
1211 |
{ |
|
1212 |
Log.InfoFormat("Completed {0}", fileName); |
|
1213 |
} |
|
1214 |
}; |
|
1215 |
return client.UploadFileTask(uri, "PUT", fileName) |
|
1216 |
.ContinueWith(upload=> |
|
1217 |
{ |
|
1218 |
client.Dispose(); |
|
1187 |
var builder = client.GetAddressBuilder(container, objectName); |
|
1188 |
var uri = builder.Uri; |
|
1219 | 1189 |
|
1220 |
if (upload.IsFaulted) |
|
1221 |
{ |
|
1222 |
var exc = upload.Exception.InnerException; |
|
1223 |
Log.ErrorFormat("[PUT] FAIL for {0} with \r{1}",objectName,exc); |
|
1224 |
throw exc; |
|
1225 |
} |
|
1226 |
else |
|
1227 |
Log.InfoFormat("[PUT] END {0}", objectName); |
|
1228 |
}); |
|
1190 |
string etag = hash ?? CalculateHash(fileName); |
|
1191 |
|
|
1192 |
client.Headers.Add("Content-Type", contentType); |
|
1193 |
client.Headers.Add("ETag", etag); |
|
1194 |
|
|
1195 |
|
|
1196 |
Log.InfoFormat("[PUT] START {0}", objectName); |
|
1197 |
client.UploadProgressChanged += (sender, args) => |
|
1198 |
{ |
|
1199 |
using (ThreadContext.Stacks["PUT"].Push("Progress")) |
|
1200 |
{ |
|
1201 |
Log.InfoFormat("{0} {1}% {2} of {3}", fileName, |
|
1202 |
args.ProgressPercentage, |
|
1203 |
args.BytesSent, args.TotalBytesToSend); |
|
1204 |
} |
|
1205 |
}; |
|
1206 |
|
|
1207 |
client.UploadFileCompleted += (sender, args) => |
|
1208 |
{ |
|
1209 |
using (ThreadContext.Stacks["PUT"].Push("Progress")) |
|
1210 |
{ |
|
1211 |
Log.InfoFormat("Completed {0}", fileName); |
|
1212 |
} |
|
1213 |
}; |
|
1214 |
if (contentType=="application/directory") |
|
1215 |
await client.UploadDataTaskAsync(uri, "PUT", new byte[0]); |
|
1216 |
else |
|
1217 |
await client.UploadFileTaskAsync(uri, "PUT", fileName); |
|
1218 |
} |
|
1219 |
|
|
1220 |
Log.InfoFormat("[PUT] END {0}", objectName); |
|
1229 | 1221 |
} |
1230 | 1222 |
catch (Exception exc) |
1231 | 1223 |
{ |
b/trunk/Pithos.Network/ICloudClient.cs | ||
---|---|---|
36 | 36 |
|
37 | 37 |
#region Object operations |
38 | 38 |
Task GetObject(string account, string container, string objectName, string fileName); |
39 |
Task PutObject(string account, string container, string objectName, string fileName, string hash = null); |
|
39 |
Task PutObject(string account, string container, string objectName, string fileName, string hash = null, string contentType = "application/octet-stream");
|
|
40 | 40 |
void DeleteObject(string account, string container, string objectName); |
41 | 41 |
//void DeleteObject(string container, string objectName, string account = null); |
42 | 42 |
void MoveObject(string account, string sourceContainer, string oldObjectName, string targetContainer, string newObjectName); |
... | ... | |
211 | 211 |
return default(Task); |
212 | 212 |
} |
213 | 213 |
|
214 |
public Task PutObject(string account, string container, string objectName, string fileName, string hash = null) |
|
214 |
public Task PutObject(string account, string container, string objectName, string fileName, string hash = null, string contentType = "application/octet-stream")
|
|
215 | 215 |
{ |
216 | 216 |
|
217 | 217 |
Contract.Requires(!String.IsNullOrWhiteSpace(Token)); |
... | ... | |
219 | 219 |
Contract.Requires(!String.IsNullOrWhiteSpace(container)); |
220 | 220 |
Contract.Requires(!String.IsNullOrWhiteSpace(fileName)); |
221 | 221 |
Contract.Requires(!String.IsNullOrWhiteSpace(objectName)); |
222 |
Contract.Requires(!String.IsNullOrWhiteSpace(contentType)); |
|
222 | 223 |
|
223 | 224 |
return default(Task); |
224 | 225 |
} |
b/trunk/Pithos.Setup.x64/Pithos.Setup.x64.vdproj | ||
---|---|---|
22 | 22 |
"Entry" |
23 | 23 |
{ |
24 | 24 |
"MsmKey" = "8:_00C78A0AE638245667A5681AAAA0F51F" |
25 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
26 |
"MsmSig" = "8:_UNDEFINED" |
|
27 |
} |
|
28 |
"Entry" |
|
29 |
{ |
|
30 |
"MsmKey" = "8:_00C78A0AE638245667A5681AAAA0F51F" |
|
31 | 25 |
"OwnerKey" = "8:_EBAF62F0335F4918AB23EDA708507D1F" |
32 | 26 |
"MsmSig" = "8:_UNDEFINED" |
33 | 27 |
} |
... | ... | |
64 | 58 |
"Entry" |
65 | 59 |
{ |
66 | 60 |
"MsmKey" = "8:_07169606B53C447F3C4DEF318E8BB147" |
67 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
|
|
61 |
"OwnerKey" = "8:_15C6D19249891DD0DF6332A0CC3AAB34"
|
|
68 | 62 |
"MsmSig" = "8:_UNDEFINED" |
69 | 63 |
} |
70 | 64 |
"Entry" |
... | ... | |
81 | 75 |
} |
82 | 76 |
"Entry" |
83 | 77 |
{ |
84 |
"MsmKey" = "8:_12F390B80DE8E9BA050029362F21B586" |
|
85 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
86 |
"MsmSig" = "8:_UNDEFINED" |
|
87 |
} |
|
88 |
"Entry" |
|
89 |
{ |
|
90 | 78 |
"MsmKey" = "8:_13807DF64A18092E2104382D6350B99E" |
91 | 79 |
"OwnerKey" = "8:_1BD6A9CD577C40098C968C8B464A03BC" |
92 | 80 |
"MsmSig" = "8:_UNDEFINED" |
... | ... | |
94 | 82 |
"Entry" |
95 | 83 |
{ |
96 | 84 |
"MsmKey" = "8:_13807DF64A18092E2104382D6350B99E" |
97 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
98 |
"MsmSig" = "8:_UNDEFINED" |
|
99 |
} |
|
100 |
"Entry" |
|
101 |
{ |
|
102 |
"MsmKey" = "8:_13807DF64A18092E2104382D6350B99E" |
|
103 | 85 |
"OwnerKey" = "8:_3455E6F41DFDCA75B1C8BAD894A467F2" |
104 | 86 |
"MsmSig" = "8:_UNDEFINED" |
105 | 87 |
} |
... | ... | |
129 | 111 |
} |
130 | 112 |
"Entry" |
131 | 113 |
{ |
132 |
"MsmKey" = "8:_15C6D19249891DD0DF6332A0CC3AAB34" |
|
133 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
134 |
"MsmSig" = "8:_UNDEFINED" |
|
135 |
} |
|
136 |
"Entry" |
|
137 |
{ |
|
138 | 114 |
"MsmKey" = "8:_1BD6A9CD577C40098C968C8B464A03BC" |
139 | 115 |
"OwnerKey" = "8:_UNDEFINED" |
140 | 116 |
"MsmSig" = "8:_UNDEFINED" |
... | ... | |
148 | 124 |
"Entry" |
149 | 125 |
{ |
150 | 126 |
"MsmKey" = "8:_2486B3A261E9B1189CF1D6B7923DEEE4" |
151 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
152 |
"MsmSig" = "8:_UNDEFINED" |
|
153 |
} |
|
154 |
"Entry" |
|
155 |
{ |
|
156 |
"MsmKey" = "8:_2486B3A261E9B1189CF1D6B7923DEEE4" |
|
157 | 127 |
"OwnerKey" = "8:_15C6D19249891DD0DF6332A0CC3AAB34" |
158 | 128 |
"MsmSig" = "8:_UNDEFINED" |
159 | 129 |
} |
... | ... | |
178 | 148 |
"Entry" |
179 | 149 |
{ |
180 | 150 |
"MsmKey" = "8:_3455E6F41DFDCA75B1C8BAD894A467F2" |
181 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
182 |
"MsmSig" = "8:_UNDEFINED" |
|
183 |
} |
|
184 |
"Entry" |
|
185 |
{ |
|
186 |
"MsmKey" = "8:_3455E6F41DFDCA75B1C8BAD894A467F2" |
|
187 | 151 |
"OwnerKey" = "8:_EBAF62F0335F4918AB23EDA708507D1F" |
188 | 152 |
"MsmSig" = "8:_UNDEFINED" |
189 | 153 |
} |
... | ... | |
196 | 160 |
"Entry" |
197 | 161 |
{ |
198 | 162 |
"MsmKey" = "8:_4DEB12AE1A97F1592C5B75ADEBF86C3D" |
199 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
200 |
"MsmSig" = "8:_UNDEFINED" |
|
201 |
} |
|
202 |
"Entry" |
|
203 |
{ |
|
204 |
"MsmKey" = "8:_4DEB12AE1A97F1592C5B75ADEBF86C3D" |
|
205 | 163 |
"OwnerKey" = "8:_EBAF62F0335F4918AB23EDA708507D1F" |
206 | 164 |
"MsmSig" = "8:_UNDEFINED" |
207 | 165 |
} |
... | ... | |
213 | 171 |
} |
214 | 172 |
"Entry" |
215 | 173 |
{ |
216 |
"MsmKey" = "8:_5907BDFBCCD9996878A22FB928EC136D" |
|
217 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
218 |
"MsmSig" = "8:_UNDEFINED" |
|
219 |
} |
|
220 |
"Entry" |
|
221 |
{ |
|
222 | 174 |
"MsmKey" = "8:_5A954DFDECEA1772693C525449B45377" |
223 | 175 |
"OwnerKey" = "8:_94AF0407772DC0469EA5A27F079132FE" |
224 | 176 |
"MsmSig" = "8:_UNDEFINED" |
... | ... | |
226 | 178 |
"Entry" |
227 | 179 |
{ |
228 | 180 |
"MsmKey" = "8:_5A954DFDECEA1772693C525449B45377" |
229 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
230 |
"MsmSig" = "8:_UNDEFINED" |
|
231 |
} |
|
232 |
"Entry" |
|
233 |
{ |
|
234 |
"MsmKey" = "8:_5A954DFDECEA1772693C525449B45377" |
|
235 | 181 |
"OwnerKey" = "8:_EBAF62F0335F4918AB23EDA708507D1F" |
236 | 182 |
"MsmSig" = "8:_UNDEFINED" |
237 | 183 |
} |
... | ... | |
244 | 190 |
"Entry" |
245 | 191 |
{ |
246 | 192 |
"MsmKey" = "8:_5BDDF78B8F57F5F0C86B681FC536679D" |
247 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
248 |
"MsmSig" = "8:_UNDEFINED" |
|
249 |
} |
|
250 |
"Entry" |
|
251 |
{ |
|
252 |
"MsmKey" = "8:_5BDDF78B8F57F5F0C86B681FC536679D" |
|
253 | 193 |
"OwnerKey" = "8:_3455E6F41DFDCA75B1C8BAD894A467F2" |
254 | 194 |
"MsmSig" = "8:_UNDEFINED" |
255 | 195 |
} |
... | ... | |
274 | 214 |
"Entry" |
275 | 215 |
{ |
276 | 216 |
"MsmKey" = "8:_5CB7E7BC9A3FBCBBAAA5B13892A9804A" |
277 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
278 |
"MsmSig" = "8:_UNDEFINED" |
|
279 |
} |
|
280 |
"Entry" |
|
281 |
{ |
|
282 |
"MsmKey" = "8:_5CB7E7BC9A3FBCBBAAA5B13892A9804A" |
|
283 | 217 |
"OwnerKey" = "8:_EBAF62F0335F4918AB23EDA708507D1F" |
284 | 218 |
"MsmSig" = "8:_UNDEFINED" |
285 | 219 |
} |
... | ... | |
310 | 244 |
"Entry" |
311 | 245 |
{ |
312 | 246 |
"MsmKey" = "8:_86C6A4ACD935E66BC53753149FB5DB8E" |
313 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
314 |
"MsmSig" = "8:_UNDEFINED" |
|
315 |
} |
|
316 |
"Entry" |
|
317 |
{ |
|
318 |
"MsmKey" = "8:_86C6A4ACD935E66BC53753149FB5DB8E" |
|
319 | 247 |
"OwnerKey" = "8:_15C6D19249891DD0DF6332A0CC3AAB34" |
320 | 248 |
"MsmSig" = "8:_UNDEFINED" |
321 | 249 |
} |
... | ... | |
333 | 261 |
} |
334 | 262 |
"Entry" |
335 | 263 |
{ |
336 |
"MsmKey" = "8:_94AF0407772DC0469EA5A27F079132FE" |
|
337 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
338 |
"MsmSig" = "8:_UNDEFINED" |
|
339 |
} |
|
340 |
"Entry" |
|
341 |
{ |
|
342 | 264 |
"MsmKey" = "8:_9C9414F9AC6F40DD9B23916692C951BE" |
343 | 265 |
"OwnerKey" = "8:_UNDEFINED" |
344 | 266 |
"MsmSig" = "8:_UNDEFINED" |
... | ... | |
352 | 274 |
"Entry" |
353 | 275 |
{ |
354 | 276 |
"MsmKey" = "8:_B0464BCF85DA5A9724AC7A145E4742A1" |
355 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
356 |
"MsmSig" = "8:_UNDEFINED" |
|
357 |
} |
|
358 |
"Entry" |
|
359 |
{ |
|
360 |
"MsmKey" = "8:_B0464BCF85DA5A9724AC7A145E4742A1" |
|
361 | 277 |
"OwnerKey" = "8:_EBAF62F0335F4918AB23EDA708507D1F" |
362 | 278 |
"MsmSig" = "8:_UNDEFINED" |
363 | 279 |
} |
... | ... | |
376 | 292 |
"Entry" |
377 | 293 |
{ |
378 | 294 |
"MsmKey" = "8:_C19B1A60C99103E4AB0CB34FBD46A86E" |
379 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
380 |
"MsmSig" = "8:_UNDEFINED" |
|
381 |
} |
|
382 |
"Entry" |
|
383 |
{ |
|
384 |
"MsmKey" = "8:_C19B1A60C99103E4AB0CB34FBD46A86E" |
|
385 | 295 |
"OwnerKey" = "8:_EBAF62F0335F4918AB23EDA708507D1F" |
386 | 296 |
"MsmSig" = "8:_UNDEFINED" |
387 | 297 |
} |
... | ... | |
417 | 327 |
} |
418 | 328 |
"Entry" |
419 | 329 |
{ |
420 |
"MsmKey" = "8:_CC30DE73FADCF70C6AD1C4570027C5C3"
|
|
421 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
|
|
330 |
"MsmKey" = "8:_CCAE7818C895B517369F69B2359B7A13"
|
|
331 |
"OwnerKey" = "8:_1BD6A9CD577C40098C968C8B464A03BC"
|
|
422 | 332 |
"MsmSig" = "8:_UNDEFINED" |
423 | 333 |
} |
424 | 334 |
"Entry" |
... | ... | |
430 | 340 |
"Entry" |
431 | 341 |
{ |
432 | 342 |
"MsmKey" = "8:_D90F384DDD0DCD7EF3877F70303E7A5E" |
433 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
434 |
"MsmSig" = "8:_UNDEFINED" |
|
435 |
} |
|
436 |
"Entry" |
|
437 |
{ |
|
438 |
"MsmKey" = "8:_D90F384DDD0DCD7EF3877F70303E7A5E" |
|
439 | 343 |
"OwnerKey" = "8:_3455E6F41DFDCA75B1C8BAD894A467F2" |
440 | 344 |
"MsmSig" = "8:_UNDEFINED" |
441 | 345 |
} |
... | ... | |
460 | 364 |
"Entry" |
461 | 365 |
{ |
462 | 366 |
"MsmKey" = "8:_DB755C1EAC71B2C6FAB0C47B84E8DC98" |
463 |
"OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364" |
|
464 |
"MsmSig" = "8:_UNDEFINED" |
|
465 |
} |
|
466 |
"Entry" |
|
467 |
{ |
|
468 |
"MsmKey" = "8:_DB755C1EAC71B2C6FAB0C47B84E8DC98" |
|
469 | 367 |
"OwnerKey" = "8:_15C6D19249891DD0DF6332A0CC3AAB34" |
470 | 368 |
"MsmSig" = "8:_UNDEFINED" |
471 | 369 |
} |
Also available in: Unified diff