<Window x:Class="Pithos.Client.WPF.FileProperties.ContainerPropertiesView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="ContainerPropertiesView" Height="300" Width="300"
- Background="#FFD4D0C8">
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:cal="http://www.caliburnproject.org"
+ Title="ContainerPropertiesView" Height="500" Width="300"
+ Background="#FFD4D0C8" Icon="/Pithos.Client.WPF;component/Images/PithosTaskbar.png">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
+ <RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="5">
<TextBlock x:Name="BlockHash" Text="2345456" Grid.Row="5" Grid.Column="1" Style="{StaticResource ResourceKey=ValueColumnStyle}"/>
</Grid>
</GroupBox>
+ <GroupBox Header="Metadata" Grid.Row="2" >
+ <DataGrid ItemsSource="{Binding Tags}"
+ AutoGenerateColumns="False" CanUserAddRows="True" >
+ <DataGrid.Columns>
+ <DataGridTextColumn Binding="{Binding Name}" Header="Name" />
+ <DataGridTextColumn Binding="{Binding Value}" Header="Value" />
+ </DataGrid.Columns>
+ </DataGrid>
+ </GroupBox>
+ <GroupBox Header="Policies" Grid.Row="3" >
+ <DataGrid ItemsSource="{Binding Policies}"
+ AutoGenerateColumns="False" CanUserAddRows="True" >
+ <DataGrid.Columns>
+ <DataGridTextColumn Binding="{Binding Name}" Header="Name" />
+ <DataGridTextColumn Binding="{Binding Value}" Header="Value" />
+ </DataGrid.Columns>
+ </DataGrid>
+ </GroupBox>
- <StackPanel Orientation="Horizontal" Grid.Row="3" HorizontalAlignment="Right">
+ <StackPanel Orientation="Horizontal" Grid.Row="4" HorizontalAlignment="Right">
<Button Name="SaveChanges" Content="OK" Margin="5,5,10,5" Style="{StaticResource ButtonStyle}"/>
<Button Name="RejectChanges" Content="Cancel" Margin="5,5,10,5" Style="{StaticResource ButtonStyle}"/>
<Button Name="ApplyChanges" Content="Apply" Style="{StaticResource ButtonStyle}" />
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.Diagnostics.Contracts;
using System.Linq;
{
_container = value;
+ Tags.Clear();
+ var tags = from tag in value.Tags
+ select new Tag(tag.Key, tag.Value);
+ tags.Apply(tag => Tags.Add(tag));
+
+
Count = value.Count;
ShortSize = value.Bytes.ToByteSize();
Size = String.Format("{0} ({1:N0} bytes)", ShortSize, value.Bytes);
}
}
-
+
+ private readonly ObservableCollection<Tag> _tags = new ObservableCollection<Tag>();
+ public ObservableCollection<Tag> Tags
+ {
+ get { return _tags; }
+ }
public ContainerPropertiesViewModel(ShellViewModel shell, ContainerInfo container, string localFolderName)
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- mc:Ignorable="d"
- d:DesignHeight="300" d:DesignWidth="300"
- Background="#FFD4D0C8" Height="481">
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:cal="http://www.caliburnproject.org"
+ mc:Ignorable="d"
+ d:DesignHeight="300" d:DesignWidth="300" MaxWidth="500"
+ Background="#FFD4D0C8" Height="481" Icon="/Pithos.Client.WPF;component/Images/PithosTaskbar.png">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
+ <RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
</Grid>
</GroupBox>
<GroupBox Header="Metadata" Grid.Row="2" >
- <ListView x:Name="GeneralProperties">
- <ListView.View>
- <GridView>
- <GridViewColumn >
- <GridViewColumn.CellTemplate>
+ <DataGrid ItemsSource="{Binding Tags}"
+ AutoGenerateColumns="False" CanUserAddRows="True" >
+ <DataGrid.Columns>
+ <DataGridTemplateColumn >
+ <DataGridTemplateColumn.CellTemplate>
+ <DataTemplate>
+ <Button Content=" - "
+ cal:Message.Attach="RemoveTag($dataContext)" />
+ </DataTemplate>
+ </DataGridTemplateColumn.CellTemplate>
+ </DataGridTemplateColumn>
+ <DataGridTextColumn Binding="{Binding Name}" Header="Name" />
+ <DataGridTextColumn Binding="{Binding Value}" Header="Value" />
+ </DataGrid.Columns>
+ </DataGrid>
+ </GroupBox>
+ <GroupBox Header="Permissions" Grid.Row="3" >
+ <DataGrid ItemsSource="{Binding Permissions}"
+ AutoGenerateColumns="False" CanUserAddRows="True">
+ <DataGrid.Columns>
+ <DataGridTemplateColumn >
+ <DataGridTemplateColumn.CellTemplate>
<DataTemplate>
- <Button Content="-" Width="20"/>
+ <Button Content=" - "
+ cal:Message.Attach="RemoveTag($dataContext)" />
</DataTemplate>
- </GridViewColumn.CellTemplate>
- </GridViewColumn>
- <GridViewColumn Header="Name"/>
- <GridViewColumn Header="Value" />
- </GridView>
- </ListView.View>
- <ListViewItem Content="Moo">
- </ListViewItem>
- </ListView>
+ </DataGridTemplateColumn.CellTemplate>
+ </DataGridTemplateColumn>
+ <DataGridTextColumn Binding="{Binding UserName}" Header="Name" />
+ <DataGridCheckBoxColumn Binding="{Binding Read}" Header="Read"/>
+ <DataGridCheckBoxColumn Binding="{Binding Write}" Header="Write"/>
+ </DataGrid.Columns>
+ </DataGrid>
</GroupBox>
- <StackPanel Orientation="Horizontal" Grid.Row="3" HorizontalAlignment="Right">
+ <StackPanel Orientation="Horizontal" Grid.Row="4" HorizontalAlignment="Right">
<Button Name="SaveChanges" Content="OK" Margin="5,5,10,5" Style="{StaticResource ButtonStyle}"/>
<Button Name="RejectChanges" Content="Cancel" Margin="5,5,10,5" Style="{StaticResource ButtonStyle}"/>
<Button Name="ApplyChanges" Content="Apply" Style="{StaticResource ButtonStyle}" />
{
_pithosFile = value;
- if (Permissions!=null)
- ((IDictionary<string,string>)Permissions).Clear();
- value.Permissions.Apply(perm=>Permissions.Add(perm.Key,perm.Value));
+ Permissions.Clear();
+ Tags.Clear();
+
+ var perms=from permission in value.Permissions
+ select new Permission(permission.Key, permission.Value);
+ perms.Apply(perm=>Permissions.Add(perm));
+
+ var tags=from tag in value.Tags
+ select new Tag(tag.Key, tag.Value);
+ tags.Apply(tag=>Tags.Add(tag));
+
Kind=value.Content_Type;
ShortSize = value.Bytes.ToByteSize();
Size = String.Format("{0} ({1:N0} bytes)", ShortSize, value.Bytes);
NotifyOfPropertyChange(()=>PithosFile);
}
}
-
- private ObservableConcurrentDictionary<string, string> _permissions;
- public ObservableConcurrentDictionary<string, string> Permissions
+
+ private readonly ObservableCollection<Tag> _tags = new ObservableCollection<Tag>();
+ public ObservableCollection<Tag> Tags
{
- get { return _permissions; }
- set
- {
- _permissions = value;
- }
+ get { return _tags; }
}
+ private readonly ObservableCollection<Permission> _permissions = new ObservableCollection<Permission>();
+ public ObservableCollection<Permission> Permissions
+ {
+ get { return _permissions; }
+ }
public void Refresh()
{
--- /dev/null
+using Caliburn.Micro;
+
+namespace Pithos.Client.WPF
+{
+ public class Permission:PropertyChangedBase
+ {
+ private string _userName;
+ public string UserName
+ {
+ get { return _userName; }
+ set
+ {
+ _userName = value;
+ NotifyOfPropertyChange(()=>UserName);
+ }
+ }
+
+ private bool _read;
+ public bool Read
+ {
+ get { return _read; }
+ set
+ {
+ _read = value;
+ if (_read)
+ Write = false;
+ NotifyOfPropertyChange(()=>Read);
+ }
+ }
+
+ private bool _write;
+
+ public bool Write
+ {
+ get { return _write; }
+ set
+ {
+ _write = value;
+ if (_write)
+ Read = false;
+ NotifyOfPropertyChange(()=>Write);
+ }
+ }
+
+ public Permission(string userName, string permission)
+ {
+ UserName = userName;
+ Read = (string.Compare(permission, "read", true) == 0);
+ Write= (string.Compare(permission, "write", true) == 0);
+ }
+
+ public Permission()
+ {
+
+ }
+
+ }
+}
\ No newline at end of file
--- /dev/null
+using Caliburn.Micro;
+
+namespace Pithos.Client.WPF
+{
+ public class Tag:PropertyChangedBase
+ {
+ private string _name;
+ public string Name
+ {
+ get { return _name; }
+ set
+ {
+ _name = value;
+ NotifyOfPropertyChange(()=>Name);
+ }
+ }
+
+ private string _value;
+ public string Value
+ {
+ get { return _value; }
+ set
+ {
+ _value = value;
+ NotifyOfPropertyChange(()=>Value);
+ }
+ }
+
+ public Tag(string name, string value)
+ {
+ Name = name;
+ Value = value;
+ }
+
+ public Tag()
+ {
+
+ }
+
+
+ }
+}
\ No newline at end of file
<CodeAnalysisRuleDirectories>;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules</CodeAnalysisRuleDirectories>
</PropertyGroup>
<ItemGroup>
- <Reference Include="AsyncCtpLibrary, Version=1.0.4107.18181, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
- <HintPath>C:\Users\Administrator\Documents\Microsoft Visual Studio Async CTP\Samples\AsyncCtpLibrary.dll</HintPath>
+ <Reference Include="AsyncCtpLibrary">
+ <HintPath>..\Libraries\AsyncCtpLibrary.dll</HintPath>
</Reference>
<Reference Include="Caliburn.Micro, Version=1.2.0.0, Culture=neutral, PublicKeyToken=8e5891231f2ed21f, processorArchitecture=MSIL">
<HintPath>..\Libraries\Caliburn.Micro.dll</HintPath>
<DependentUpon>FilePropertiesView.xaml</DependentUpon>
</Compile>
<Compile Include="FileProperties\FilePropertiesViewModel.cs" />
+ <Compile Include="FileProperties\Permission.cs" />
<Compile Include="FileProperties\SizeExtensions.cs" />
+ <Compile Include="FileProperties\Tag.cs" />
<Compile Include="NativeMethods.cs" />
<Compile Include="Notification.cs" />
<Compile Include="PithosAccount.cs" />
<ItemGroup>
<Resource Include="Images\Container.png" />
</ItemGroup>
+ <ItemGroup>
+ <Resource Include="Images\PithosTaskbar.png" />
+ </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<CodeAnalysisRuleDirectories>;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules</CodeAnalysisRuleDirectories>
</PropertyGroup>
<ItemGroup>
- <Reference Include="AsyncCtpLibrary, Version=1.0.4107.18181, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
- <HintPath>C:\Users\Administrator\Documents\Microsoft Visual Studio Async CTP\Samples\AsyncCtpLibrary.dll</HintPath>
+ <Reference Include="AsyncCtpLibrary">
+ <HintPath>..\Libraries\AsyncCtpLibrary.dll</HintPath>
</Reference>
<Reference Include="Castle.ActiveRecord, Version=3.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL" />
<Reference Include="Castle.Core">
var filePath = CloudFile.RelativeUrlToFilePath(AccountInfo.UserName);
return Path.Combine(AccountInfo.AccountPath, filePath);
}
+
+ public override string ToString()
+ {
+ return String.Format("{0}:{1}->{2}", this.Action, this.LocalFile.FullName, this.CloudFile.Name);
+ }
}
public class CloudDownloadAction:CloudAction
public CloudDownloadAction(AccountInfo accountInfo, ObjectInfo cloudFile)
:base(accountInfo,CloudActionType.DownloadUnconditional)
{
+ if (String.IsNullOrWhiteSpace(cloudFile.Container))
+ throw new ArgumentException("CloudFile.Container","cloudFile");
+ Contract.EndContractBlock();
+
CloudFile = cloudFile;
}
+
+ [ContractInvariantMethod]
+ private void Invariants()
+ {
+ Contract.Invariant(!String.IsNullOrWhiteSpace(CloudFile.Container));
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0}: _ <- {1}", this.Action, this.CloudFile.Name);
+ }
}
public class CloudDeleteAction:CloudAction
{
- public CloudDeleteAction(AccountInfo accountInfo, string fileName, FileState fileState)
- : this(accountInfo,new ObjectInfo { Name = fileName },fileState)
+ public CloudDeleteAction(AccountInfo accountInfo,FileInfo fileInfo, FileState fileState)
+ : this(accountInfo,new ObjectInfo(accountInfo.AccountPath,accountInfo.UserName,fileInfo),fileState)
{
}
public CloudDeleteAction(CloudAction action)
: this(action.AccountInfo,action.CloudFile,action.FileState)
{}
+
+ [ContractInvariantMethod]
+ private void Invariants()
+ {
+ Contract.Invariant(!String.IsNullOrWhiteSpace(CloudFile.Container));
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0}: _ ->{1}", this.Action, this.CloudFile.Name);
+ }
+
}
public class CloudUploadAction:CloudAction
{
- public CloudUploadAction(AccountInfo accountInfo, FileInfo fileInfo, FileState state, int blockSize, string algorithm)
- : base(accountInfo,CloudActionType.UploadUnconditional,fileInfo,ObjectInfo.Empty,state,blockSize,algorithm)
+ public CloudUploadAction(AccountInfo accountInfo, FileInfo fileInfo, FileState state, int blockSize, string algorithm)
+ : base(accountInfo, CloudActionType.UploadUnconditional, fileInfo,
+ new ObjectInfo(accountInfo.AccountPath, accountInfo.UserName,fileInfo), state, blockSize, algorithm)
{
- var relativeUrl = fileInfo.AsRelativeUrlTo(accountInfo.AccountPath);
- //The first part of the URL is the container
- var slashIndex = relativeUrl.IndexOf('/');
- var container = relativeUrl.Substring(0, slashIndex);
- //The second is the file's url relative to the container
- var fileUrl = relativeUrl.Substring(slashIndex + 1);
+ }
- CloudFile = new ObjectInfo {Account = accountInfo.UserName, Container = container, Name = fileUrl};
+ [ContractInvariantMethod]
+ private void Invariants()
+ {
+ Contract.Invariant(!String.IsNullOrWhiteSpace(CloudFile.Container));
}
+
}
public class CloudMoveAction:CloudAction
{
- public string OldFileName { get; set; }
- public string OldPath { get; set; }
- public string NewFileName { get; set; }
- public string NewPath { get; set; }
+ public ObjectInfo OldCloudFile { get; set; }
+
+ public FileInfo OldLocalFile { get; set; }
- public CloudMoveAction(AccountInfo accountInfo, CloudActionType action, string oldPath, string oldFileName, string newFileName, string newPath)
+ public CloudMoveAction(AccountInfo accountInfo, CloudActionType action, FileInfo oldFile, FileInfo newFile)
:base(accountInfo,action)
{
- OldFileName = oldFileName;
- OldPath = oldPath;
- NewFileName = newFileName;
- NewPath = newPath;
+ LocalFile = newFile;
+ CloudFile = new ObjectInfo(accountInfo.AccountPath, accountInfo.UserName, newFile);
+
+ OldLocalFile = oldFile;
+ OldCloudFile = new ObjectInfo(accountInfo.AccountPath, accountInfo.UserName, oldFile);
+
//This is a rename operation, a hash will not be used
LocalHash = new Lazy<string>(() => String.Empty, LazyThreadSafetyMode.ExecutionAndPublication);
}
+ public override string ToString()
+ {
+ return String.Format("{0}:{1}->{2}", this.Action, OldCloudFile.Name, CloudFile.Name);
+ }
}
{
static class FileInfoExtensions
{
- public static string AsRelativeTo(this FileInfo fileInfo,string path )
- {
- if (String.IsNullOrWhiteSpace(path))
- throw new ArgumentNullException("path");
- Contract.EndContractBlock();
-
-
- if (!path.EndsWith("\\"))
- path=path.ToLower() + "\\";
- int pathLength = path.Length;
-
- var filePath = fileInfo.FullName;
-
- if (!filePath.StartsWith(path,StringComparison.InvariantCultureIgnoreCase))
- throw new ArgumentException(String.Format("The path {0} doesn't contain the file {1}",path,filePath));
-
- var relativePath = filePath.Substring(pathLength, filePath.Length - pathLength);
-
- return relativePath;
- }
-
- public static string AsRelativeUrlTo(this FileInfo fileInfo,string path )
- {
- if (String.IsNullOrWhiteSpace(path))
- throw new ArgumentNullException("path");
- Contract.EndContractBlock();
-
- var relativePath = fileInfo.AsRelativeTo(path);
- var replacedSlashes = relativePath.Replace("\\","/");
- var escaped = Uri.EscapeUriString(replacedSlashes);
- return escaped;
- }
-
- public static string RelativeUriToFilePath(this Uri uri)
- {
- var unescaped = Uri.UnescapeDataString(uri.ToString());
- var path = unescaped.Replace("/", "\\");
- return path;
- }
-
+
public static int Read(this FileInfo fileInfo,byte[] buffer,int offset,int count)
{
});
}
- private Task<object> Process(CloudAction action)
+ private async Task Process(CloudAction action)
{
if (action == null)
throw new ArgumentNullException("action");
using (log4net.ThreadContext.Stacks["NETWORK"].Push("PROCESS"))
{
- Log.InfoFormat("[ACTION] Start Processing {0}:{1}->{2}", action.Action, action.LocalFile,
- action.CloudFile.Name);
+ Log.InfoFormat("[ACTION] Start Processing {0}", action);
var localFile = action.LocalFile;
var cloudFile = action.CloudFile;
switch (action.Action)
{
case CloudActionType.UploadUnconditional:
- UploadCloudFile(action);
+ await UploadCloudFile(action);
break;
case CloudActionType.DownloadUnconditional:
- DownloadCloudFile(accountInfo, cloudFile,downloadPath);
+ await DownloadCloudFile(accountInfo, cloudFile,downloadPath);
break;
case CloudActionType.DeleteCloud:
DeleteCloudFile(accountInfo, cloudFile, cloudFile.Name);
break;
case CloudActionType.RenameCloud:
var moveAction = (CloudMoveAction)action;
- RenameCloudFile(accountInfo, cloudFile, moveAction);
+ RenameCloudFile(accountInfo, moveAction);
break;
case CloudActionType.MustSynch:
if (!File.Exists(downloadPath))
{
- DownloadCloudFile(accountInfo, cloudFile, downloadPath);
+ await DownloadCloudFile(accountInfo, cloudFile, downloadPath);
}
else
{
- SyncFiles(accountInfo, action);
+ await SyncFiles(accountInfo, action);
}
break;
}
action.Action, action.LocalFile, action.CloudFile, exc);
_agent.Post(action);
- }
- return CompletedTask<object>.Default;
+ }
}
}
- private void SyncFiles(AccountInfo accountInfo,CloudAction action)
+ private async Task SyncFiles(AccountInfo accountInfo,CloudAction action)
{
if (accountInfo == null)
throw new ArgumentNullException("accountInfo");
{
case FileStatus.Unchanged:
//If the local file's status is Unchanged, we can go on and download the newer cloud file
- DownloadCloudFile(accountInfo,cloudFile,downloadPath);
+ await DownloadCloudFile(accountInfo,cloudFile,downloadPath);
break;
case FileStatus.Modified:
//If the local file is Modified, we may have a conflict. In this case we should mark the file as Conflict
}
- private void RenameCloudFile(AccountInfo accountInfo,ObjectInfo cloudFile,CloudMoveAction action)
+ private void RenameCloudFile(AccountInfo accountInfo,CloudMoveAction action)
{
if (accountInfo==null)
throw new ArgumentNullException("accountInfo");
- if (cloudFile==null)
- throw new ArgumentNullException("cloudFile");
if (action==null)
throw new ArgumentNullException("action");
- if (String.IsNullOrWhiteSpace(action.CloudFile.Container))
- throw new ArgumentException("Invalid container", "action");
+ if (action.CloudFile==null)
+ throw new ArgumentException("CloudFile","action");
+ if (action.LocalFile==null)
+ throw new ArgumentException("LocalFile","action");
+ if (action.OldLocalFile==null)
+ throw new ArgumentException("OldLocalFile","action");
+ if (action.OldCloudFile==null)
+ throw new ArgumentException("OldCloudFile","action");
Contract.EndContractBlock();
+
+ var newFilePath = action.LocalFile.FullName;
//The local file is already renamed
- this.StatusKeeper.SetFileOverlayStatus(action.NewPath, FileOverlayStatus.Modified);
+ this.StatusKeeper.SetFileOverlayStatus(newFilePath, FileOverlayStatus.Modified);
var account = action.CloudFile.Account ?? accountInfo.UserName;
- var container = action.CloudFile.Container;// ?? FolderConstants.PithosContainer;
+ var container = action.CloudFile.Container;
var client = new CloudFilesClient(accountInfo);
- client.MoveObject(account, container, action.OldFileName, container, action.NewFileName);
+ client.MoveObject(account, container, action.OldCloudFile.Name, container, action.CloudFile.Name);
- this.StatusKeeper.SetFileStatus(action.NewPath, FileStatus.Unchanged);
- this.StatusKeeper.SetFileOverlayStatus(action.NewPath, FileOverlayStatus.Normal);
- NativeMethods.RaiseChangeNotification(action.NewPath);
+ this.StatusKeeper.SetFileStatus(newFilePath, FileStatus.Unchanged);
+ this.StatusKeeper.SetFileOverlayStatus(newFilePath, FileOverlayStatus.Normal);
+ NativeMethods.RaiseChangeNotification(newFilePath);
}
private void DeleteCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile,string fileName)
}
//Download a file.
- private void DownloadCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile , string localPath)
+ private async Task DownloadCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile , string localPath)
{
if (accountInfo == null)
throw new ArgumentNullException("accountInfo");
if (!Path.IsPathRooted(localPath))
throw new ArgumentException("The localPath must be rooted", "localPath");
Contract.EndContractBlock();
-
- var download=Task.Factory.Iterate(DownloadIterator(accountInfo,cloudFile, localPath));
- download.Wait();
- }
-
- private IEnumerable<Task> DownloadIterator(AccountInfo accountInfo,ObjectInfo cloudFile, string localPath)
- {
- if (accountInfo == null)
- throw new ArgumentNullException("accountInfo");
- if (cloudFile == null)
- throw new ArgumentNullException("cloudFile");
- if (String.IsNullOrWhiteSpace(cloudFile.Account))
- throw new ArgumentNullException("cloudFile");
- if (String.IsNullOrWhiteSpace(cloudFile.Container))
- throw new ArgumentNullException("cloudFile");
- if (String.IsNullOrWhiteSpace(localPath))
- throw new ArgumentNullException("localPath");
- if (!Path.IsPathRooted(localPath))
- throw new ArgumentException("The localPath must be rooted", "localPath");
- Contract.EndContractBlock();
-
+
Uri relativeUrl = new Uri(cloudFile.Name, UriKind.Relative);
var url = relativeUrl.ToString();
if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))
- yield break;
+ return;
//Are we already downloading or uploading the file?
using (var gate=NetworkGate.Acquire(localPath, NetworkOperation.Downloading))
{
if (gate.Failed)
- yield break;
+ return;
//The file's hashmap will be stored in the same location with the extension .hashmap
//var hashPath = Path.Combine(FileAgent.CachePath, relativePath + ".hashmap");
var container = cloudFile.Container;
//Retrieve the hashmap from the server
- var getHashMap = client.GetHashMap(account, container, url);
- yield return getHashMap;
-
- var serverHash=getHashMap.Result;
+ var serverHash = await client.GetHashMap(account, container, url);
//If it's a small file
- var downloadTask=(serverHash.Hashes.Count == 1 )
+ if (serverHash.Hashes.Count == 1 )
//Download it in one go
- ? DownloadEntireFile(accountInfo,client, cloudFile, relativeUrl, localPath, serverHash)
+ await DownloadEntireFile(accountInfo, client, cloudFile, relativeUrl, localPath, serverHash);
//Otherwise download it block by block
- : DownloadWithBlocks(accountInfo,client, cloudFile, relativeUrl, localPath, serverHash);
-
- yield return downloadTask;
+ else
+ await DownloadWithBlocks(accountInfo,client, cloudFile, relativeUrl, localPath, serverHash);
if (cloudFile.AllowedTo == "read")
{
}
//Download a small file with a single GET operation
- private Task DownloadEntireFile(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string localPath,TreeHash serverHash)
+ private async Task DownloadEntireFile(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string localPath,TreeHash serverHash)
{
if (client == null)
throw new ArgumentNullException("client");
var localMD5 = Signature.CalculateMD5(localPath);
var cloudHash=serverHash.TopHash.ToHashString();
if (localMD5==cloudHash)
- return CompletedTask.Default;
+ return;
//Then check with a treehash
var localTreeHash = Signature.CalculateTreeHash(localPath, serverHash.BlockSize, serverHash.BlockHash);
var localHash = localTreeHash.TopHash.ToHashString();
if (localHash==cloudHash)
- return CompletedTask.Default;
+ return;
}
var fileAgent = GetFileAgent(accountInfo);
Directory.CreateDirectory(tempFolder);
//Download the object to the temporary location
- var getObject = client.GetObject(cloudFile.Account, cloudFile.Container, relativeUrl.ToString(), tempPath).ContinueWith(t =>
+ await client.GetObject(cloudFile.Account, cloudFile.Container, relativeUrl.ToString(), tempPath).ContinueWith(t =>
{
t.PropagateExceptions();
//Create the local folder if it doesn't exist (necessary for shared objects)
//Notify listeners that a local file has changed
StatusNotification.NotifyChangedFile(localPath);
- });
- return getObject;
+ });
}
//Download a file asynchronously using blocks
- public Task DownloadWithBlocks(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string localPath, TreeHash serverHash)
+ public async Task DownloadWithBlocks(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string localPath, TreeHash serverHash)
{
if (client == null)
throw new ArgumentNullException("client");
throw new ArgumentNullException("serverHash");
Contract.EndContractBlock();
- return Task.Factory.Iterate(BlockDownloadIterator(accountInfo,client,cloudFile, relativeUrl, localPath, serverHash));
- }
-
- private IEnumerable<Task> BlockDownloadIterator(AccountInfo accountInfo,CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string localPath, TreeHash serverHash)
- {
- if (client == null)
- throw new ArgumentNullException("client");
- if (cloudFile==null)
- throw new ArgumentNullException("cloudFile");
- if (relativeUrl == null)
- throw new ArgumentNullException("relativeUrl");
- if (String.IsNullOrWhiteSpace(localPath))
- throw new ArgumentNullException("localPath");
- if (!Path.IsPathRooted(localPath))
- throw new ArgumentException("The localPath must be rooted", "localPath");
- if(serverHash==null)
- throw new ArgumentNullException("serverHash");
- Contract.EndContractBlock();
-
- var fileAgent = GetFileAgent(accountInfo);
+ var fileAgent = GetFileAgent(accountInfo);
//Calculate the relative file path for the new file
var relativePath = relativeUrl.RelativeUriToFilePath();
//Calculate the file's treehash
- var calcHash = Signature.CalculateTreeHashAsync(localPath, serverHash.BlockSize,serverHash.BlockHash);
- yield return calcHash;
- var treeHash = calcHash.Result;
+ var treeHash = await Signature.CalculateTreeHashAsync(localPath, serverHash.BlockSize, serverHash.BlockHash);
//And compare it with the server's hash
var upHashes = serverHash.GetHashesAsStrings();
end= ((i + 1)*serverHash.BlockSize) ;
//Download the missing block
- var getBlock = client.GetBlock(cloudFile.Account, cloudFile.Container, relativeUrl, start, end);
- yield return getBlock;
- var block = getBlock.Result;
+ var block = await client.GetBlock(cloudFile.Account, cloudFile.Container, relativeUrl, start, end);
//and store it
- yield return blockUpdater.StoreBlock(i, block);
+ blockUpdater.StoreBlock(i, block);
Log.InfoFormat("[BLOCK GET] FINISH {0} of {1} for {2}", i, upHashes.Length, localPath);
}
- private async void UploadCloudFile(CloudAction action)
+ private async Task UploadCloudFile(CloudAction action)
{
if (action == null)
throw new ArgumentNullException("action");
var treeHash = await Signature.CalculateTreeHashAsync(fileInfo.FullName, accountInfo.BlockSize,
accountInfo.BlockHash);
- UploadWithHashMap(accountInfo,cloudFile,fileInfo,cloudFile.Name,treeHash);
+ await UploadWithHashMap(accountInfo,cloudFile,fileInfo,cloudFile.Name,treeHash);
//If everything succeeds, change the file and overlay status to normal
this.StatusKeeper.SetFileState(fullFileName, FileStatus.Unchanged, FileOverlayStatus.Normal);
}
- public async void UploadWithHashMap(AccountInfo accountInfo,ObjectInfo cloudFile,FileInfo fileInfo,string url,TreeHash treeHash)
+ public async Task UploadWithHashMap(AccountInfo accountInfo,ObjectInfo cloudFile,FileInfo fileInfo,string url,TreeHash treeHash)
{
if (accountInfo == null)
throw new ArgumentNullException("accountInfo");
var fullFileName = fileInfo.FullName;
var account = cloudFile.Account ?? accountInfo.UserName;
- var container = cloudFile.Container ;//?? FolderConstants.PithosContainer;
+ var container = cloudFile.Container ;
var client = new CloudFilesClient(accountInfo);
//Send the hashmap to the server
NetworkAgent.Post(new CloudUploadAction(accountInfo,info, fileState, accountInfo.BlockSize,
accountInfo.BlockHash));
break;
- case FileStatus.Deleted:
- string fileName = info.AsRelativeUrlTo(accountInfo.AccountPath);
- NetworkAgent.Post(new CloudDeleteAction(accountInfo,fileName, fileState));
+ case FileStatus.Deleted:
+ NetworkAgent.Post(new CloudDeleteAction(accountInfo,info, fileState));
break;
case FileStatus.Renamed:
- NetworkAgent.Post(new CloudMoveAction(accountInfo,CloudActionType.RenameCloud, state.OldFileName,
- state.OldPath, state.FileName, state.Path));
+ NetworkAgent.Post(new CloudMoveAction(accountInfo,CloudActionType.RenameCloud, new FileInfo(state.OldPath),
+ new FileInfo(state.Path)));
break;
}
</PropertyGroup>
<ItemGroup>
<Reference Include="AsyncCtpLibrary">
- <HintPath>C:\Users\Administrator\Documents\Microsoft Visual Studio Async CTP\Samples\AsyncCtpLibrary.dll</HintPath>
+ <HintPath>..\Libraries\AsyncCtpLibrary.dll</HintPath>
</Reference>
<Reference Include="Caliburn.Micro, Version=1.2.0.0, Culture=neutral, PublicKeyToken=8e5891231f2ed21f, processorArchitecture=MSIL">
<HintPath>..\Libraries\Caliburn.Micro.dll</HintPath>
if (status == FileStatus.Created)
return oldStatus;
break;
- case FileStatus.Deleted:
- return oldStatus;
}
StatusKeeper.SetFileStatus(path, status);
return status;
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Linq;
+using System.Text;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+
+namespace Pithos.Interfaces
+{
+ public static class FileInfoExtensions
+ {
+ public static string AsRelativeTo(this FileInfo fileInfo,string path )
+ {
+ if (String.IsNullOrWhiteSpace(path))
+ throw new ArgumentNullException("path");
+ Contract.EndContractBlock();
+
+
+ if (!path.EndsWith("\\"))
+ path=path.ToLower() + "\\";
+ int pathLength = path.Length;
+
+ var filePath = fileInfo.FullName;
+
+ if (!filePath.StartsWith(path,StringComparison.InvariantCultureIgnoreCase))
+ throw new ArgumentException(String.Format("The path {0} doesn't contain the file {1}",path,filePath));
+
+ var relativePath = filePath.Substring(pathLength, filePath.Length - pathLength);
+
+ return relativePath;
+ }
+
+ public static string AsRelativeUrlTo(this FileInfo fileInfo,string path )
+ {
+ if (String.IsNullOrWhiteSpace(path))
+ throw new ArgumentNullException("path");
+ Contract.EndContractBlock();
+
+ var relativePath = fileInfo.AsRelativeTo(path);
+ var replacedSlashes = relativePath.Replace("\\","/");
+ var escaped = Uri.EscapeUriString(replacedSlashes);
+ return escaped;
+ }
+
+ public static string RelativeUriToFilePath(this Uri uri)
+ {
+ var unescaped = Uri.UnescapeDataString(uri.ToString());
+ var path = unescaped.Replace("/", "\\");
+ return path;
+ }
+
+
+
+ }
+}
public string Container { get; set; }
+ public ObjectInfo()
+ {}
+
+ public ObjectInfo(string accountPath,string accountName,FileInfo fileInfo)
+ {
+ var relativeUrl = fileInfo.AsRelativeUrlTo(accountPath);
+ //The first part of the URL is the container
+ var slashIndex = relativeUrl.IndexOf('/');
+ var container = relativeUrl.Substring(0, slashIndex);
+ //The second is the file's url relative to the container
+ var fileUrl = relativeUrl.Substring(slashIndex + 1);
+
+ Account = accountName;
+ Container = container;
+ Name = fileUrl;
+ }
+
private void ExtractKnownExtensions()
{
</ItemGroup>
<ItemGroup>
<Compile Include="AccountSettings.cs" />
+ <Compile Include="FileInfoExtensions.cs" />
<Compile Include="IPithosSettings.cs" />
<Compile Include="IStatusChecker.cs" />
<Compile Include="PermissionConverter.cs" />
</PropertyGroup>
<ItemGroup>
<Reference Include="AsyncCtpLibrary">
- <HintPath>C:\Users\Administrator\Documents\Microsoft Visual Studio Async CTP\Samples\AsyncCtpLibrary.dll</HintPath>
+ <HintPath>..\Libraries\AsyncCtpLibrary.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b9a188c8922137c6, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
{
case HttpStatusCode.OK:
case HttpStatusCode.NoContent:
+ var keys = client.ResponseHeaders.AllKeys.AsQueryable();
+ var tags = (from key in keys
+ where key.StartsWith("X-Container-Meta-")
+ let name = key.Substring(14)
+ select new { Name = name, Value = client.ResponseHeaders[name] })
+ .ToDictionary(t => t.Name, t => t.Value);
+ var policies= (from key in keys
+ where key.StartsWith("X-Container-Policy-")
+ let name = key.Substring(14)
+ select new { Name = name, Value = client.ResponseHeaders[name] })
+ .ToDictionary(t => t.Name, t => t.Value);
+
var containerInfo = new ContainerInfo
{
Account=account,
Bytes = long.Parse(client.GetHeaderValue("X-Container-Bytes-Used")),
BlockHash = client.GetHeaderValue("X-Container-Block-Hash"),
BlockSize=int.Parse(client.GetHeaderValue("X-Container-Block-Size")),
- Last_Modified=client.LastModified
+ Last_Modified=client.LastModified,
+ Tags=tags,
+ Policies=policies
};
+
+
return containerInfo;
case HttpStatusCode.NotFound:
return ContainerInfo.Empty;
using System;
+using System.Collections.Generic;
namespace Pithos.Network
{
public DateTime Last_Modified { get; set; }
+ public Dictionary<string, string> Tags { get; set; }
+ public Dictionary<string, string> Policies { get; set; }
+
public static ContainerInfo Empty=new ContainerInfo();
+
+ public ContainerInfo()
+ {
+ Tags = new Dictionary<string, string>();
+ }
}
}
\ No newline at end of file
</PropertyGroup>
<ItemGroup>
<Reference Include="AsyncCtpLibrary">
- <HintPath>C:\Users\Administrator\Documents\Microsoft Visual Studio Async CTP\Samples\AsyncCtpLibrary.dll</HintPath>
+ <HintPath>..\Libraries\AsyncCtpLibrary.dll</HintPath>
</Reference>
<Reference Include="log4net">
<HintPath>..\Libraries\log4net.dll</HintPath>
using System.Threading.Tasks;
using log4net;
+
namespace Pithos.Network
{
using System;
protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult result)
{
- return ProcessResponse(()=>base.GetWebResponse(request, result));
+ return ProcessResponse(()=>base.GetWebResponse(request, result));
}
protected override WebResponse GetWebResponse(WebRequest request)
public string DownloadStringWithRetry(string address,int retries=0)
{
+
if (address == null)
throw new ArgumentNullException("address");
TraceStart("GET",actualAddress);
var actualRetries = (retries == 0) ? Retries : retries;
-
var task = Retry(() =>