--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using Pithos.Client.WPF.Utils;
+
+namespace Pithos.Client.WPF.Test
+{
+ [TestFixture]
+ class NodeTest
+ {
+ [Test]
+ public void TestIteration()
+ {
+ var root = new Node<int>{Path = "Root",
+ Children =
+ {
+ new Node<int> {Path = "Root/Path1",
+ Children =
+ {
+ new Node<int>{Path="Root/Path1/Path11",
+ Children=
+ {
+ new Node<int>{Path="Root/Path1/Path11/Path111",
+ Children=
+ {
+ new Node<int>{Path="Root/Path1/Path11/Path111/File1"}
+ }}
+ }
+ },
+ new Node<int>{Path="Root/Path1/File2"}
+ }
+ },
+ }
+ };
+ Assert.That(root.Count(), Is.EqualTo(6));
+ }
+
+ [Test]
+ public void TestEquals()
+ {
+ var target = new Node<int>{Path = "Root",
+ Children =
+ {
+ new Node<int> {Path = "Root/Path1",
+ Children =
+ {
+ new Node<int>{Path="Root/Path1/Path11",
+ Children=
+ {
+ new Node<int>{Path="Root/Path1/Path11/Path111",
+ Children=
+ {
+ new Node<int>{Path="Root/Path1/Path11/Path111/File1"}
+ }}
+ }
+ },
+ new Node<int>{Path="Root/Path1/File2"}
+ }
+ },
+ }
+ };
+ var source= new Node<int>{Path = "Root",
+ Children =
+ {
+ new Node<int>{Path = "Root/Path1",
+ Children =
+ {
+ new Node<int>{Path="Root/Path1/Path11",
+ Children=
+ {
+ new Node<int>{Path="Root/Path1/Path11/Path111",
+ Children=
+ {
+ new Node<int>{Path="Root/Path1/Path11/Path111/File1"}
+ }}
+ }
+ },
+ new Node<int>{Path="Root/Path1/File2"}
+ }
+ },
+ }
+ };
+ Assert.That(source.Equals(target), Is.True);
+ }
+
+ [Test]
+ public void TestToTree()
+ {
+ var target = new Node<int>{Path = "Root",
+ Children =
+ {
+ new Node<int>{Path = "Root/Path1",
+ Children =
+ {
+ new Node<int>{Path="Root/Path1/File2"},
+ new Node<int>{Path="Root/Path1/Path11",
+ Children=
+ {
+ new Node<int>{Path="Root/Path1/Path11/Path111",
+ Children=
+ {
+ new Node<int>{Path="Root/Path1/Path11/Path111/File1"}
+ }}
+ }
+ },
+ }
+ },
+ }
+ };
+ var source= new[]
+ {
+ Tuple.Create("Root",0),
+ Tuple.Create("Root/Path1",0),
+ Tuple.Create("Root/Path1/Path11",0),
+ Tuple.Create("Root/Path1/Path11/Path111",0),
+ Tuple.Create("Root/Path1/Path11/Path111/File1",0),
+ Tuple.Create("Root/Path1/File2",0)
+ };
+
+ Assert.That(source.ToTree(s=>s.Item1,s=>s.Item2).First().Equals(target), Is.True);
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{7B5BFE77-FC4D-43B3-84A0-9CB457238951}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Pithos.Client.WPF.Test</RootNamespace>
+ <AssemblyName>Pithos.Client.WPF.Test</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Caliburn.Micro, Version=1.2.0.0, Culture=neutral, PublicKeyToken=8e5891231f2ed21f, processorArchitecture=MSIL" />
+ <Reference Include="nunit.framework">
+ <HintPath>..\packages\NUnit.2.5.10.11092\lib\nunit.framework.dll</HintPath>
+ </Reference>
+ <Reference Include="nunit.mocks">
+ <HintPath>..\packages\NUnit.2.5.10.11092\lib\nunit.mocks.dll</HintPath>
+ </Reference>
+ <Reference Include="pnunit.framework">
+ <HintPath>..\packages\NUnit.2.5.10.11092\lib\pnunit.framework.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="NodeTest.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\Pithos.Client.WPF\Pithos.Client.WPF.csproj">
+ <Project>{4D9406A3-50ED-4672-BB97-A0B3EA4946FE}</Project>
+ <Name>Pithos.Client.WPF</Name>
+ </ProjectReference>
+ </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.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file
--- /dev/null
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Pithos.Client.WPF.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("HP")]
+[assembly: AssemblyProduct("Pithos.Client.WPF.Test")]
+[assembly: AssemblyCopyright("Copyright © HP 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("75c60907-2488-4b7b-9b03-2e923a17e92a")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="NUnit" version="2.5.10.11092" />
+</packages>
\ No newline at end of file
--- /dev/null
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using System.Linq;
+using System.Windows.Markup;
+
+namespace Pithos.Client.WPF.Converters
+{
+ public class EnumTypeConverter : EnumConverter
+ {
+ public EnumTypeConverter(Type enumType) : base(enumType) { }
+
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ if (destinationType == typeof(string) && value != null)
+ {
+ var enumType = value.GetType();
+ if (enumType.IsEnum)
+ return GetDisplayName(value);
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ private string GetDisplayName(object enumValue)
+ {
+ var displayNameAttribute = EnumType.GetField(enumValue.ToString())
+ .GetCustomAttributes(typeof(DescriptionAttribute), false)
+ .FirstOrDefault() as DescriptionAttribute;
+ if (displayNameAttribute != null)
+ return displayNameAttribute.Description;
+
+ return Enum.GetName(EnumType, enumValue);
+ }
+ }
+
+ [MarkupExtensionReturnType(typeof(object[]))]
+ public class EnumValuesExtension : MarkupExtension
+ {
+ public EnumValuesExtension()
+ {
+ }
+
+ public EnumValuesExtension(Type enumType)
+ {
+ EnumType = enumType;
+ }
+
+ [ConstructorArgument("enumType")]
+ public Type EnumType { get; set; }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ if (EnumType == null)
+ throw new ArgumentException("The enum type is not set");
+ return Enum.GetValues(EnumType);
+ }
+ }
+
+}
--- /dev/null
+<Window x:Class="Pithos.Client.WPF.FileProperties.ConflictsView"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:cal="http://www.caliburnproject.org"
+ xmlns:cnv="clr-namespace:Pithos.Client.WPF.Converters"
+ xmlns:model="clr-namespace:Pithos.Client.WPF.FileProperties"
+ Title="Conflicts" Height="300" Width="500" x:Name="This"
+ >
+ <Window.Resources>
+ <ResourceDictionary>
+ <ContextMenu x:Key="RowMenu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
+ <MenuItem Header="{Binding FilePath}" cal:Message.Attach="[Event MouseLeftButtonUp]=[Action GoToFile($dataContext)]"
+ cal:Action.TargetWithoutContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}, Path=DataContext}"/>
+ </ContextMenu>
+ <Style x:Key="DefaultRowStyle" TargetType="{x:Type DataGridRow}">
+ <Setter Property="ContextMenu" Value="{StaticResource RowMenu}" />
+ </Style>
+ <ResourceDictionary.MergedDictionaries>
+ <ResourceDictionary Source="..\PithosStyles.xaml" />
+ </ResourceDictionary.MergedDictionaries>
+ </ResourceDictionary>
+ </Window.Resources>
+ <Grid>
+ <Grid.RowDefinitions>
+ <RowDefinition />
+ <RowDefinition Height="Auto"/>
+ </Grid.RowDefinitions>
+
+ <ListBox x:Name="Conflicts" HorizontalContentAlignment="Stretch" Grid.Row="0">
+ <ListBox.ItemTemplate>
+ <DataTemplate>
+ <Grid>
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition />
+ <ColumnDefinition Width="Auto"/>
+ </Grid.ColumnDefinitions>
+ <TextBlock x:Name="FilePath" Text="{Binding FilePath}" Grid.Row="0" Margin="5"/>
+ <ComboBox x:Name="Action" ItemsSource="{cnv:EnumValues model:ConflictAction}"
+ SelectedValue="{Binding Action}" Grid.Row="1"
+ HorizontalAlignment="Right"
+ HorizontalContentAlignment="Left"
+ Width="110" Margin="5" Padding="5,2"
+ />
+ </Grid>
+ </DataTemplate>
+ </ListBox.ItemTemplate>
+ </ListBox>
+ <StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Right">
+ <Button Name="Apply" Content="OK" Margin="5,5,10,5" Style="{StaticResource ButtonStyle}" IsDefault="False" />
+ <Button Name="Cancel" Content="Cancel" Margin="5,5,10,5" Style="{StaticResource ButtonStyle}" IsCancel="True" />
+ </StackPanel>
+
+ </Grid>
+</Window>
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace Pithos.Client.WPF.FileProperties
+{
+ /// <summary>
+ /// Interaction logic for ConflictsView.xaml
+ /// </summary>
+ public partial class ConflictsView : Window
+ {
+ public ConflictsView()
+ {
+ InitializeComponent();
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.Composition;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Caliburn.Micro;
+using Pithos.Client.WPF.Converters;
+using Pithos.Core;
+using Pithos.Interfaces;
+
+namespace Pithos.Client.WPF.FileProperties
+{
+ [TypeConverter(typeof(EnumTypeConverter))]
+ public enum ConflictAction
+ {
+ [Description("Defer Decision")]
+ Defer,
+ [Description("Keep Local")]
+ KeepLocal,
+ [Description("Keep Server")]
+ KeepServer,
+ [Description("Keep Both")]
+ KeepBoth,
+ [Description("Clear Record")]
+ ClearLocal
+ }
+
+ public class ConflictFile:PropertyChangedBase
+ {
+ private string _filePath;
+ public string FilePath
+ {
+ get { return _filePath; }
+ set
+ {
+ _filePath = value;
+ NotifyOfPropertyChange(()=>FilePath);
+ }
+ }
+
+ private ConflictAction _action;
+
+ public ConflictAction Action
+ {
+ get { return _action; }
+ set
+ {
+ _action = value;
+ NotifyOfPropertyChange(()=>Action);
+ }
+ }
+ }
+ [Export(typeof(ConflictsViewModel))]
+ class ConflictsViewModel:Screen
+ {
+ [Import]
+ public IStatusKeeper StatusKeeper { get; set; }
+
+ [Import]
+ public IConflictResolver Resolver { get; set; }
+
+ private readonly ObservableCollection<ConflictFile> _conflicts;
+
+ public ObservableCollection<ConflictFile> Conflicts
+ {
+ get { return _conflicts; }
+ }
+
+ public string[] Actions
+ {
+ get { return new[] {"Keep Local", "Keep Server", "Keep Both"}; }
+ }
+
+ public ConflictsViewModel()
+ {
+ this.DisplayName="Conflicts";
+ var conflicts = from state in FileState.Queryable
+ /*where state.FileStatus == FileStatus.Conflict ||
+ state.OverlayStatus == FileOverlayStatus.Conflict*/
+ select new ConflictFile {FilePath = state.FilePath};
+ _conflicts = new ObservableCollection<ConflictFile>(conflicts.ToList());
+
+ }
+
+ /// <summary>
+ /// Open an explorer window to the target path's directory
+ /// and select the file
+ /// </summary>
+ /// <param name="entry"></param>
+ public void GoToFile(string fullPath)
+ {
+ if (!File.Exists(fullPath) && !Directory.Exists(fullPath))
+ return;
+ Process.Start("explorer.exe", "/select, " + fullPath);
+ }
+
+ public void Apply()
+ {
+ var conflicts = from conflict in Conflicts
+ where conflict.Action != ConflictAction.Defer
+ select conflict;
+ Resolver.Resolve(conflicts);
+
+ TryClose(true);
+ }
+
+ public void Cancel()
+ {
+ TryClose(false);
+ }
+
+
+ }
+
+ internal interface IConflictResolver
+ {
+ void Resolve(IEnumerable<ConflictFile> conflicts);
+ }
+}
<Reference Include="Caliburn.Micro, Version=1.2.0.0, Culture=neutral, PublicKeyToken=8e5891231f2ed21f, processorArchitecture=MSIL">
<HintPath>..\Libraries\Caliburn.Micro.dll</HintPath>
</Reference>
+ <Reference Include="Castle.ActiveRecord, Version=3.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL" />
<Reference Include="log4net">
<HintPath>..\Libraries\log4net.dll</HintPath>
</Reference>
</ApplicationDefinition>
<Compile Include="Converters\DummyConverter.cs" />
<Compile Include="Converters\EmptyToVisibilityConverter.cs" />
+ <Compile Include="Converters\EnumTypeConverter.cs" />
<Compile Include="Converters\NullToVisibilityConverter.cs" />
<Compile Include="Converters\SingleLineConverter.cs" />
+ <Compile Include="FileProperties\ConflictsView.xaml.cs">
+ <DependentUpon>ConflictsView.xaml</DependentUpon>
+ </Compile>
+ <Compile Include="FileProperties\ConflictsViewModel.cs" />
<Compile Include="FileProperties\ContainerPolicy.cs" />
<Compile Include="FileProperties\NewContainerView.xaml.cs">
<DependentUpon>NewContainerView.xaml</DependentUpon>
</Compile>
<Compile Include="Shell\ShellViewModel.cs" />
<Compile Include="Services\StatusService.cs" />
+ <Compile Include="Utils\EnumerableExtensions.cs" />
+ <Compile Include="Utils\Node.cs" />
<Compile Include="Wpf32Window.cs" />
+ <Page Include="FileProperties\ConflictsView.xaml">
+ <SubType>Designer</SubType>
+ <Generator>MSBuild:Compile</Generator>
+ </Page>
<Page Include="FileProperties\NewContainerView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
[assembly: AssemblyCopyright("Copyright © GRNet 2011-2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-[assembly: AssemblyInformationalVersion("2012-04-03")]
+[assembly: AssemblyInformationalVersion("2012-04-06")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.7.20403.0")]
-[assembly: AssemblyFileVersionAttribute("0.7.20403.0")]
+[assembly: AssemblyVersion("0.7.20406.0")]
+[assembly: AssemblyFileVersionAttribute("0.7.20406.0")]
using System.Linq;
using System.Text;
using Caliburn.Micro;
+using Pithos.Client.WPF.Utils;
using Pithos.Core.Agents;
using Pithos.Interfaces;
namespace Pithos.Client.WPF.SelectiveSynch
{
- public class DirectoryRecord : PropertyChangedBase,IEnumerable<DirectoryRecord>
+ public class DirectoryRecord :PropertyChangedBase, IEnumerable<DirectoryRecord>
{
private ObjectInfo _objectInfo;
public ObjectInfo ObjectInfo
{
get { return _objectInfo; }
set
- {
+ {
_objectInfo = value;
Uri = value.Uri;
}
public bool IsInitiallySelected { get; private set; }
- /*readonly Lazy<List<DirectoryRecord>> _directories = new Lazy<List<DirectoryRecord>>();
-
+ private List<DirectoryRecord> _directories=new List<DirectoryRecord>();
public List<DirectoryRecord> Directories
{
- get
- {
- return _directories.Value;
- }
- }*/
-
- private IEnumerable<DirectoryRecord> _directories=new List<DirectoryRecord>();
- public IEnumerable<DirectoryRecord> Directories
- {
get { return _directories; }
- set { _directories = value; }
+ set { _directories= value; }
}
public DirectoryRecord()
}
private string _displayName;
+
public string DisplayName
{
get
set { _displayName = value; }
}
- public DirectoryRecord(string rootPath,ObjectInfo info)
+ public DirectoryRecord(ObjectInfo info)
{
- var relativePath = info.RelativeUrlToFilePath(info.Account);
- //LocalInfo = new DirectoryInfo(Path.Combine(rootPath, relativePath));
ObjectInfo = info;
}
}
*/
+
public IEnumerator<DirectoryRecord> GetEnumerator()
{
yield return this;
{
return GetEnumerator();
}
+
+ public override int GetHashCode()
+ {
+ return ObjectInfo.GetHashCode();
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (!(obj is DirectoryRecord))
+ return false;
+ var other = (DirectoryRecord)obj;
+ if (Uri != other.Uri)
+ return false;
+ if (ObjectInfo== null ^ other.ObjectInfo== null)
+ return false;
+
+ if (ObjectInfo!= null && !ObjectInfo.Equals(other.ObjectInfo))
+ return false;
+ var thisEnum = GetEnumerator();
+ var otherEnum = other.GetEnumerator();
+ //Skipt the first item, it is the current node itself
+ thisEnum.MoveNext();
+ otherEnum.MoveNext();
+ while (true)
+ {
+ var thisMove = thisEnum.MoveNext();
+ var otherMove = otherEnum.MoveNext();
+
+ if (thisMove ^ otherMove)
+ return false;
+ if (!thisMove)
+ return true;
+
+ if (!thisEnum.Current.Equals(otherEnum.Current))
+ return false;
+ }
+ }
}
}
xmlns:local="clr-namespace:Pithos.Client.WPF.SelectiveSynch"
xmlns:extToolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit/extended"
xmlns:Converters="clr-namespace:Pithos.Client.WPF.Converters"
- Title="Selective Synch" Height="300" Width="300"
+ Title="Selective Synch" Height="500" Width="500"
ShowInTaskbar="true"
WindowStartupLocation="CenterScreen"
Icon="/PithosPlus;component/Images/PithosTaskbar.png"
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<extToolkit:BusyIndicator Name="IsBusy" Grid.Row="0" BusyContent="Retrieving folders ..." DisplayAfter="0">
- <TreeView Name="RootNodes"
+ <TreeView Name="RootNodes" Margin="5"
ItemContainerStyle="{StaticResource TreeItemStyle}"
ItemTemplate="{StaticResource CheckboxStyle}">
</TreeView>
using System.Threading.Tasks;
using Caliburn.Micro;
using Pithos.Client.WPF.Properties;
+using Pithos.Client.WPF.Utils;
using Pithos.Core;
using Pithos.Interfaces;
{
var root = RootNodes[0];
_checks = new ObservableCollection<ObjectInfo>(
- from record in root
+ from DirectoryRecord record in root
where record.IsChecked==true
select record.ObjectInfo);
NotifyOfPropertyChange(() => Checks);
{
Account = account;
AccountName = account.AccountName;
- DisplayName = account.AccountName;
+ DisplayName = String.Format("Selective folder synchronization for {0}",account.AccountName);
_monitor = monitor;
_events = events;
TaskEx.Run(LoadRootNode);
{
var client = _monitor.CloudClient;
- var dirs = from container in client.ListContainers(_monitor.UserName)
+ var dirs = from container in client.ListContainers(_monitor.UserName)
select new DirectoryRecord
{
DisplayName = container.Name,
Uri=new Uri(client.StorageUrl,container.Name),
- Directories = (from dir in client.ListObjects(_monitor.UserName, container.Name, "")
+ Directories = (from dir in client.ListObjects(_monitor.UserName, container.Name)
where dir.Content_Type == DirectoryType
- select new DirectoryRecord { DisplayName = dir.Name, ObjectInfo = dir }).ToList()
+ select dir).ToTree()
};
var ownFolders = dirs.ToList();
{
DisplayName=container.Name,
Uri = new Uri(client.StorageUrl, "../" + account.name + "/" + container.Name),
- Directories=(from folder in client.ListObjects(account.name,container.Name,"")
+ Directories=(from folder in client.ListObjects(account.name,container.Name)
where folder.Content_Type==DirectoryType
- select new DirectoryRecord{DisplayName=folder.Name,ObjectInfo=folder}).ToList()
+ select folder).ToTree()
}).ToList()
};
//Initially, all nodes are checked
//We need to *uncheck* the nodes that are not selected
- var selects = from rootRecord in RootNodes
- from record in rootRecord
+ var selects = from DirectoryRecord rootRecord in RootNodes
+ from DirectoryRecord record in rootRecord
where record.Uri !=null && !selections.Contains(record.Uri.ToString())
select record;
public void SaveChanges()
{
- var uris = (from root in RootNodes
- from record in root
+ var uris = (from DirectoryRecord root in RootNodes
+ from DirectoryRecord record in root
where record.IsChecked == true && record.Uri != null
select record.Uri).ToArray();
SaveSettings(uris);
//RootNodes is an ObservableCollection, it can't be enumerated iterativelly
-
- var added= (from root in RootNodes
- from record in root
+
+ var added = (from DirectoryRecord root in RootNodes
+ from DirectoryRecord record in root
where record.Added && record.Uri != null
select record.Uri).ToArray();
- var removed = (from root in RootNodes
- from record in root
+ var removed = (from DirectoryRecord root in RootNodes
+ from DirectoryRecord record in root
where record.Removed && record.Uri != null
select record.Uri).ToArray();
//TODO: Include Uris for the containers as well
Version = fileVersion.FileVersion;
Bits = Environment.Is64BitProcess ? "64 bit" : "32 bit";
+
+ DisplayName = "About Pithos+";
}
public void CloseAbout()
<Window x:Class="Pithos.Client.WPF.Shell.FeedbackView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="FeedbackView" Height="389" Width="455" xmlns:my="clr-namespace:Pithos.Client.WPF.Converters" Icon="/PithosPlus;component/Images/PithosTaskbar.png">
+ Height="389" Width="455" xmlns:my="clr-namespace:Pithos.Client.WPF.Converters" Icon="/PithosPlus;component/Images/PithosTaskbar.png">
<Window.Resources>
<my:EmptyToVisibilityConverter x:Key="EmptyToVisible" />
</Window.Resources>
[ImportingConstructor]
public FeedbackViewModel(IWindowManager windowManager)
{
+ DisplayName = "Send Feedback";
_windowManager = windowManager;
Data = GetBasicData() ;
}
<Separator Visibility="{Binding Path=HasAccounts, Converter={StaticResource BooleanToVisible}}"/>
<MenuItem Header="{Binding PauseSyncCaption}" x:Name="ToggleSynching" cal:Message.Attach="ToggleSynching" Visibility="{Binding Path=HasAccounts, Converter={StaticResource BooleanToVisible}}"/>
<Separator />
+ <MenuItem x:Name="ShowConflicts" Header="Show Conflicts" Visibility="{Binding Path=HasConflicts, Converter={StaticResource BooleanToVisible}}" cal:Message.Attach="ShowConflicts" />
<MenuItem Header="Preferences ..." x:Name="ShowPreferences" cal:Message.Attach="ShowPreferences" />
<Separator />
<MenuItem Header="Send Feedback" x:Name="SendFeedback" cal:Message.Attach="SendFeedback">
NotifyOfPropertyChange(()=>MiniStatusCaption);
}
+ public bool HasConflicts
+ {
+ get { return true; }
+ }
+ public void ShowConflicts()
+ {
+ _windowManager.ShowWindow(new ConflictsViewModel());
+ }
+
/// <summary>
/// Open an explorer window to the target path's directory
/// and select the file
SetPithosStatus(status);
}
+ /* public Notifier GetNotifier(Notification startNotification, Notification endNotification)
+ {
+ return new Notifier(this, startNotification, endNotification);
+ }*/
+ public Notifier GetNotifier(string startMessage, string endMessage, params object[] args)
+ {
+ return new Notifier(this,
+ new StatusNotification(String.Format(startMessage,args)),
+ new StatusNotification(String.Format(endMessage,args)));
+ }
- ///<summary>
+
+ ///<summary>
/// Updates the visual status indicators of the application depending on status changes, e.g. icon, stat
///</summary>
public void UpdateStatus()
--- /dev/null
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using Pithos.Client.WPF.SelectiveSynch;
+using Pithos.Interfaces;
+
+namespace Pithos.Client.WPF.Utils
+{
+ public static class EnumerableExtensions
+ {
+ public static IEnumerable<T> Slice<T>(this IEnumerable<T> collection, int start, int end)
+ {
+ int index = 0;
+ int count = 0;
+
+ if (collection == null)
+ throw new ArgumentNullException("collection");
+
+ // Optimise item count for ICollection interfaces.
+ if (collection is ICollection<T>)
+ count = ((ICollection<T>)collection).Count;
+ else if (collection is ICollection)
+ count = ((ICollection)collection).Count;
+ else
+ {
+ count = collection.Count();
+ }
+
+ // Get start/end indexes, negative numbers start at the end of the collection.
+ if (start < 0)
+ start += count;
+
+ if (end < 0)
+ end += count;
+
+ foreach (var item in collection)
+ {
+ if (index >= end)
+ yield break;
+
+ if (index >= start)
+ yield return item;
+
+ ++index;
+ }
+ }
+
+ public static IEnumerable<Node<T>> ToTree<TSource,T>(this IEnumerable<TSource> enumerable,Func<TSource,string> pathFunc,Func<TSource,T> valueFunc,string delimiter="/")
+ {
+ var orderedItems=enumerable.OrderBy(pathFunc);
+ var lookups = new Dictionary<string,Node<T>>();
+ var nodes = new List<Node<T>>();
+ foreach (var item in orderedItems)
+ {
+ var path = pathFunc(item);
+ var value = valueFunc(item);
+ var newNode = new Node<T> { Path = path,Data=value };
+ lookups[path] = newNode;
+
+ var lastIndex = path.LastIndexOf(delimiter, StringComparison.Ordinal);
+ var upTo = lastIndex < 0 ? path.Length - 1 : lastIndex;
+ var parentPath = path.Substring(0, upTo);
+
+ Node<T> parent;
+ if (lookups.TryGetValue(parentPath, out parent))
+ {
+ parent.Children.Add(newNode);
+ parent.Children.Sort((x,y)=>String.CompareOrdinal(x.Path, y.Path));
+ }
+ else
+ nodes.Add(newNode);
+
+ }
+ return nodes;
+ }
+
+ public static List<DirectoryRecord> ToTree(this IEnumerable<ObjectInfo> enumerable)
+ {
+ var orderedItems=enumerable.OrderBy(o=>o.Uri.ToString());
+ var lookups = new Dictionary<string,DirectoryRecord>();
+ var nodes = new List<DirectoryRecord>();
+ foreach (var item in orderedItems)
+ {
+ var path = item.Uri.ToString();
+ var newNode = new DirectoryRecord{ DisplayName=item.Name,ObjectInfo=item};
+ lookups[path] = newNode;
+
+ var lastIndex = path.LastIndexOf("/", StringComparison.Ordinal);
+ var upTo = lastIndex < 0 ? path.Length - 1 : lastIndex;
+ var parentPath = path.Substring(0, upTo);
+
+ DirectoryRecord parent;
+ if (lookups.TryGetValue(parentPath, out parent))
+ {
+ parent.Directories.Add(newNode);
+ parent.Directories.Sort((x,y)=>String.CompareOrdinal(x.Uri.ToString(), y.Uri.ToString()));
+ }
+ else
+ nodes.Add(newNode);
+
+ }
+ return nodes;
+ }
+ }
+}
--- /dev/null
+using System.Collections;
+using System.Collections.Generic;
+using Caliburn.Micro;
+
+namespace Pithos.Client.WPF.Utils
+{
+ public class Node<T> : IEnumerable<Node<T>>
+ {
+ public string Path { get; set; }
+
+ public T Data { get; set; }
+
+ private List<Node<T>> _children = new List<Node<T>>();
+ public List<Node<T>> Children
+ {
+ get { return _children; }
+ set { _children = value; }
+ }
+
+ public IEnumerator<Node<T>> GetEnumerator()
+ {
+ yield return this;
+ foreach (var children in _children)
+ foreach (var info in children)
+ {
+ yield return info;
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public override int GetHashCode()
+ {
+ return Path.GetHashCode();
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (!(obj is Node<T>))
+ return false;
+ var other = (Node<T>) obj;
+ if (Path != other.Path)
+ return false;
+ if (Data==null ^ other.Data==null)
+ return false;
+
+ if (Data!=null && !Data.Equals(other.Data))
+ return false;
+ var thisEnum=GetEnumerator();
+ var otherEnum = other.GetEnumerator();
+ //Skipt the first item, it is the current node itself
+ thisEnum.MoveNext();
+ otherEnum.MoveNext();
+ while(true)
+ {
+ var thisMove=thisEnum.MoveNext();
+ var otherMove=otherEnum.MoveNext();
+
+ if (thisMove ^ otherMove)
+ return false;
+ if (!thisMove)
+ return true;
+
+ if (!thisEnum.Current.Equals(otherEnum.Current))
+ return false;
+ }
+ }
+ }
+}
\ No newline at end of file
if (Directory.Exists(path))
return state;
-
var info = new FileInfo(path);
- StatusNotification.Notify(new StatusNotification(String.Format("Hashing [{0}]",info.Name)));
- var shortHash = info.ComputeShortHash();
-
- string merkleHash = info.CalculateHash(StatusKeeper.BlockSize,StatusKeeper.BlockHash);
- StatusKeeper.UpdateFileChecksum(path,shortHash, merkleHash);
+ using (StatusNotification.GetNotifier("Hashing {0}", "Finished Hashing {0}", info.Name))
+ {
- state.Hash = merkleHash;
- return state;
+ var shortHash = info.ComputeShortHash();
+
+ string merkleHash = info.CalculateHash(StatusKeeper.BlockSize, StatusKeeper.BlockHash);
+ StatusKeeper.UpdateFileChecksum(path, shortHash, merkleHash);
+
+ state.Hash = merkleHash;
+ return state;
+ }
}
//Does the file exist in the container's local folder?
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Pithos.Core
+{
+ public class Notifier:IDisposable
+ {
+ private readonly IStatusNotification _statusNotification;
+
+ private readonly Notification _endNotification;
+
+ public Notifier(IStatusNotification statusNotification,string startMessage,string endMessage)
+ :this(statusNotification,new Notification{Message=startMessage},new Notification{Message=endMessage} )
+ {
+
+ }
+
+ public Notifier(IStatusNotification statusNotification,Notification startNotification,Notification endNotification)
+ {
+ _statusNotification = statusNotification;
+ _endNotification = endNotification;
+ _statusNotification.Notify(startNotification);
+ }
+
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ~Notifier()
+ {
+ Dispose(false);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _statusNotification.Notify(_endNotification);
+ }
+ }
+ }
+}
var cloudHash = cloudInfo.Hash.ToLower();
- StatusNotification.Notify(new StatusNotification(String.Format("Hashing {0} for Upload", fileInfo.Name)));
-
- var treeHash = action.TreeHash.Value;
- var topHash = treeHash.TopHash.ToHashString();
+ string topHash;
+ TreeHash treeHash;
+ using(StatusNotification.GetNotifier("Hashing {0} for Upload", "Finished hashing {0}",fileInfo.Name))
+ {
+ treeHash = action.TreeHash.Value;
+ topHash = treeHash.TopHash.ToHashString();
+ }
//If the file hashes match, abort the upload
if (cloudInfo != ObjectInfo.Empty && topHash == cloudHash)
throw new ArgumentException("Invalid container", "cloudFile");
Contract.EndContractBlock();
- StatusNotification.Notify(new StatusNotification(String.Format("Uploading {0}", fileInfo.Name)));
+ using (StatusNotification.GetNotifier("Uploading {0}", "Finished Uploading {0}", fileInfo.Name))
+ {
- var fullFileName = fileInfo.GetProperCapitalization();
+ var fullFileName = fileInfo.GetProperCapitalization();
- var account = cloudFile.Account ?? accountInfo.UserName;
- var container = cloudFile.Container;
+ var account = cloudFile.Account ?? accountInfo.UserName;
+ var container = cloudFile.Container;
- var client = new CloudFilesClient(accountInfo);
- //Send the hashmap to the server
- var missingHashes = await client.PutHashMap(account, container, url, treeHash);
- int block = 0;
- ReportUploadProgress(fileInfo.Name, block++, missingHashes.Count, fileInfo.Length);
- //If the server returns no missing hashes, we are done
- while (missingHashes.Count > 0)
- {
-
- var buffer = new byte[accountInfo.BlockSize];
- foreach (var missingHash in missingHashes)
+ var client = new CloudFilesClient(accountInfo);
+ //Send the hashmap to the server
+ var missingHashes = await client.PutHashMap(account, container, url, treeHash);
+ int block = 0;
+ ReportUploadProgress(fileInfo.Name, block++, missingHashes.Count, fileInfo.Length);
+ //If the server returns no missing hashes, we are done
+ while (missingHashes.Count > 0)
{
- //Find the proper block
- var blockIndex = treeHash.HashDictionary[missingHash];
- long offset = blockIndex * accountInfo.BlockSize;
-
- var read = fileInfo.Read(buffer, offset, accountInfo.BlockSize);
- try
- {
- //And upload the block
- await client.PostBlock(account, container, buffer, 0, read);
- Log.InfoFormat("[BLOCK] Block {0} of {1} uploaded", blockIndex, fullFileName);
- }
- catch (Exception exc)
+ var buffer = new byte[accountInfo.BlockSize];
+ foreach (var missingHash in missingHashes)
{
- Log.Error(String.Format("Uploading block {0} of {1}", blockIndex, fullFileName), exc);
+ //Find the proper block
+ var blockIndex = treeHash.HashDictionary[missingHash];
+ long offset = blockIndex*accountInfo.BlockSize;
+
+ var read = fileInfo.Read(buffer, offset, accountInfo.BlockSize);
+
+ try
+ {
+ //And upload the block
+ await client.PostBlock(account, container, buffer, 0, read);
+ Log.InfoFormat("[BLOCK] Block {0} of {1} uploaded", blockIndex, fullFileName);
+ }
+ catch (Exception exc)
+ {
+ Log.Error(String.Format("Uploading block {0} of {1}", blockIndex, fullFileName), exc);
+ }
+ ReportUploadProgress(fileInfo.Name, block++, missingHashes.Count, fileInfo.Length);
}
- ReportUploadProgress(fileInfo.Name, block++, missingHashes.Count, fileInfo.Length);
+
+ //Repeat until there are no more missing hashes
+ missingHashes = await client.PutHashMap(account, container, url, treeHash);
}
- //Repeat until there are no more missing hashes
- missingHashes = await client.PutHashMap(account, container, url, treeHash);
+ ReportUploadProgress(fileInfo.Name, missingHashes.Count, missingHashes.Count, fileInfo.Length);
}
-
- ReportUploadProgress(fileInfo.Name, missingHashes.Count, missingHashes.Count, fileInfo.Length);
}
private void ReportUploadProgress(string fileName, int block, int totalBlocks, long fileSize)
void Notify(Notification notification);\r
void SetPithosStatus(PithosStatus status);\r
void SetPithosStatus(PithosStatus localSyncing, string format);\r
+ //Notifier GetNotifier(Notification startNotification, Notification endNotification);\r
+ Notifier GetNotifier(string startMessage, string endMessage,params object[] args);\r
}\r
\r
public class Notification\r
<Compile Include="Agents\IdleBatch.cs" />
<Compile Include="Agents\MovedEventArgs.cs" />
<Compile Include="Agents\NetworkAgent.cs" />
+ <Compile Include="Agents\Notifier.cs" />
<Compile Include="Agents\ObjectInfoComparer.cs" />
<Compile Include="Agents\PollAgent.cs" />
<Compile Include="Agents\SnapshotDifferencer.cs" />
try
{
- var result = task.Result;
+ var result = task.Result;
return result;
}
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetSparkle2010", "NetSparkle\NetSparkle2010.csproj", "{74635A21-2BAD-4522-AB95-E3E5703CD301}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pithos.Client.WPF.Test", "Pithos.Client.WPF.Test\Pithos.Client.WPF.Test.csproj", "{7B5BFE77-FC4D-43B3-84A0-9CB457238951}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug All|Any CPU = Debug All|Any CPU
{74635A21-2BAD-4522-AB95-E3E5703CD301}.Test|Mixed Platforms.Build.0 = Release|Any CPU
{74635A21-2BAD-4522-AB95-E3E5703CD301}.Test|x64.ActiveCfg = Release|Any CPU
{74635A21-2BAD-4522-AB95-E3E5703CD301}.Test|x86.ActiveCfg = Release|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Debug All|Any CPU.ActiveCfg = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Debug All|Any CPU.Build.0 = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Debug All|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Debug All|Mixed Platforms.Build.0 = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Debug All|x64.ActiveCfg = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Debug All|x86.ActiveCfg = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Premium Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Premium Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Premium Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Premium Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Premium Debug|x64.ActiveCfg = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Premium Debug|x86.ActiveCfg = Debug|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Release|x64.ActiveCfg = Release|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Release|x86.ActiveCfg = Release|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Test|Any CPU.ActiveCfg = Release|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Test|Any CPU.Build.0 = Release|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Test|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Test|Mixed Platforms.Build.0 = Release|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Test|x64.ActiveCfg = Release|Any CPU
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951}.Test|x86.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
{2CFE2DF1-20AE-47E2-B1BB-36B974600BE1} = {B5DD7C4D-D396-4C55-A8D5-DCFE865AA095}
{E027200B-C26A-4877-BFD9-1A18CF5DF2F4} = {B5DD7C4D-D396-4C55-A8D5-DCFE865AA095}
{F9AF3E97-BCB7-46B7-8014-7FC858AEE9BA} = {B5DD7C4D-D396-4C55-A8D5-DCFE865AA095}
+ {7B5BFE77-FC4D-43B3-84A0-9CB457238951} = {B5DD7C4D-D396-4C55-A8D5-DCFE865AA095}
EndGlobalSection
EndGlobal
<?xml version="1.0" encoding="utf-8"?>
<repositories>
+ <repository path="..\Pithos.Client.Test\packages.config" />
+ <repository path="..\Pithos.Client.WPF.Test\packages.config" />
+ <repository path="..\Pithos.Client.WPF\packages.config" />
<repository path="..\Pithos.Client\packages.config" />
<repository path="..\Pithos.Core.Test\packages.config" />
+ <repository path="..\Pithos.Core\packages.config" />
<repository path="..\Pithos.Network.Test\packages.config" />
<repository path="..\Pithos.ShellExtensions.Test\packages.config" />
- <repository path="..\Pithos.Client.Test\packages.config" />
- <repository path="..\Pithos.Core\packages.config" />
- <repository path="..\Pithos.Client.WPF\packages.config" />
</repositories>
\ No newline at end of file