Added UI for SelectiveSynch
authorPanagiotis Kanavos <pkanavos@gmail.com>
Tue, 18 Oct 2011 17:04:16 +0000 (20:04 +0300)
committerPanagiotis Kanavos <pkanavos@gmail.com>
Tue, 18 Oct 2011 17:04:16 +0000 (20:04 +0300)
Tested multi-user and shared objects.

14 files changed:
trunk/Pithos.Client.WPF/Pithos.Client.WPF.csproj
trunk/Pithos.Client.WPF/PreferencesView.xaml
trunk/Pithos.Client.WPF/PreferencesViewModel.cs
trunk/Pithos.Client.WPF/SelectiveSynch/DirectoryRecord.cs [new file with mode: 0644]
trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchChanges.cs [new file with mode: 0644]
trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchView.xaml [new file with mode: 0644]
trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchView.xaml.cs [new file with mode: 0644]
trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchViewModel.cs [new file with mode: 0644]
trunk/Pithos.Client.WPF/SelectiveSynch/VirtualToggleButton.cs [new file with mode: 0644]
trunk/Pithos.Client.WPF/ShellViewModel.cs
trunk/Pithos.Core/Agents/FileAgent.cs
trunk/Pithos.Core/Agents/NetworkAgent.cs
trunk/Pithos.Core/PithosMonitor.cs
trunk/Pithos.Network/CloudFilesClient.cs

index 3f357fe..2ce40e7 100644 (file)
       <DependentUpon>PreferencesView.xaml</DependentUpon>
     </Compile>
     <Compile Include="PreferencesViewModel.cs" />
+    <Compile Include="SelectiveSynch\DirectoryRecord.cs" />
+    <Compile Include="SelectiveSynch\SelectiveSynchChanges.cs" />
+    <Compile Include="SelectiveSynch\SelectiveSynchView.xaml.cs">
+      <DependentUpon>SelectiveSynchView.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="SelectiveSynch\SelectiveSynchViewModel.cs" />
+    <Compile Include="SelectiveSynch\VirtualToggleButton.cs" />
     <Compile Include="Services\Events.cs" />
     <Compile Include="ShellViewModel.cs" />
     <Compile Include="Services\StatusService.cs" />
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
+    <Page Include="SelectiveSynch\SelectiveSynchView.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
     <Page Include="ShellView.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
   <ItemGroup>
     <Resource Include="Images\TraySyncPaused.ico" />
   </ItemGroup>
+  <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.
index 51b8ed1..4f3e0d6 100644 (file)
                             </Grid>
                             <CheckBox Name="CurrentAccount_IsActive" Content="Account is Active" Grid.Column="1" Grid.Row="3" Margin="5"/>
                             <CheckBox Name="CurrentAccount_UsePithos" Content="Use Pithos Extensions" Grid.Column="1" Grid.Row="4" Margin="5"/>
-                            <Button Name="SelectSyncFolders" Content="Selective Sync" Width="Auto" HorizontalAlignment="Left" Style="{StaticResource ButtonStyle}" Grid.Column="1" Grid.Row="5"/>
+                            <Button Name="SelectiveSyncFolders" Content="Selective Sync" Width="Auto" HorizontalAlignment="Left" Style="{StaticResource ButtonStyle}" Grid.Column="1" Grid.Row="5"/>
                             
                         </Grid>
                         
index bcae81b..acdb412 100644 (file)
@@ -20,6 +20,7 @@ using System.Windows.Interop;
 using Caliburn.Micro;
 using Hardcodet.Wpf.TaskbarNotification;
 using Pithos.Client.WPF.Configuration;
+using Pithos.Client.WPF.SelectiveSynch;
 using Pithos.Core;
 using Pithos.Interfaces;
 using IWin32Window = System.Windows.Forms.IWin32Window;
@@ -55,11 +56,11 @@ namespace Pithos.Client.WPF
         
         public ShellViewModel Shell { get;  set; }
         //ShellExtensionController _extensionController=new ShellExtensionController();
-        
-        public PreferencesViewModel(IEventAggregator events,ShellViewModel shell, PithosSettings settings)
+
+        public PreferencesViewModel(IWindowManager windowManager, IEventAggregator events, ShellViewModel shell, PithosSettings settings)
         {
+            _windowManager = windowManager;
             _events = events;
-            _events.Subscribe(this);
 
             DisplayName = "Pithos Preferences";
             Shell = shell;
@@ -69,23 +70,7 @@ namespace Pithos.Client.WPF
 
         }
 
-/*
-        protected override void OnViewAttached(object view, object context)
-        {
-            var window = (Window)view;
-
-            base.OnViewAttached(view, context);
-        }
-
 
-        protected override void OnViewLoaded(object view)
-        {
-            var window = (Window)view;
-            window.Hide();
-            base.OnViewLoaded(view);
-        }
-
-*/
         #region Preferences Properties
 
         private bool _noProxy;
@@ -129,7 +114,14 @@ namespace Pithos.Client.WPF
        
         #region Commands
         
-
+        public void SelectiveSyncFolders()
+        {
+            var model = new SelectiveSynchViewModel(_events,CurrentAccount);
+            if (_windowManager.ShowDialog(model) == true)
+            {
+                
+            }
+        }
     
         public void SaveChanges()
         {
@@ -272,6 +264,8 @@ namespace Pithos.Client.WPF
         }
 
         private AccountSettings _currentAccount;
+        private IWindowManager _windowManager;
+
         public AccountSettings CurrentAccount
         {
             get {
diff --git a/trunk/Pithos.Client.WPF/SelectiveSynch/DirectoryRecord.cs b/trunk/Pithos.Client.WPF/SelectiveSynch/DirectoryRecord.cs
new file mode 100644 (file)
index 0000000..e634a04
--- /dev/null
@@ -0,0 +1,138 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Caliburn.Micro;
+
+namespace Pithos.Client.WPF.SelectiveSynch
+{
+    public class DirectoryRecord : PropertyChangedBase,IEnumerable<DirectoryRecord>
+    {
+        public DirectoryInfo Info { get; set; }
+
+
+        DirectoryRecord _parent;
+
+        public bool Added { get; set; }
+        public bool Removed { get; set; }
+
+        private bool? _isChecked;
+        #region IsChecked
+
+        /// <summary>
+        /// Gets/sets the state of the associated UI toggle (ex. CheckBox).
+        /// The return value is calculated based on the check state of all
+        /// child FooViewModels.  Setting this property to true or false
+        /// will set all children to the same check state, and setting it 
+        /// to any value will cause the parent to verify its check state.
+        /// </summary>
+        public bool? IsChecked
+        {
+            get { return _isChecked; }
+            set { this.SetIsChecked(value, true, true); }
+        }
+
+        void SetIsChecked(bool? value, bool updateChildren, bool updateParent)
+        {
+            if (value == _isChecked)
+                return;
+
+            _isChecked = value;
+
+            //If the value is null both Added and Removed should be False
+            Added = _isChecked??false;
+            Removed = !(_isChecked??true);
+
+            if (updateChildren && _isChecked.HasValue)
+                this.Directories.ForEach(c => c.SetIsChecked(_isChecked, true, false));
+
+            if (updateParent && _parent != null)
+                _parent.VerifyCheckState();
+
+            this.RaisePropertyChangedEventImmediately("IsChecked");
+        }
+
+        void VerifyCheckState()
+        {
+            bool? state = null;
+            for (int i = 0; i < this.Directories.Count; ++i)
+            {
+                bool? current = this.Directories[i].IsChecked;
+                if (i == 0)
+                {
+                    state = current;
+                }
+                else if (state != current)
+                {
+                    state = null;
+                    break;
+                }
+            }
+            this.SetIsChecked(state, false, true);
+        }
+
+        #endregion // IsChecked
+
+
+        public bool IsInitiallySelected { get; private set; }
+
+        readonly Lazy<List<DirectoryRecord>> _directories = new Lazy<List<DirectoryRecord>>();
+
+        public List<DirectoryRecord> Directories
+        {
+            get
+            {
+                return _directories.Value;
+            }
+        }
+
+        public DirectoryRecord(string ignorePath)
+        {
+            _directories = new Lazy<List<DirectoryRecord>>(() => 
+                (from directory in Info.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
+                where !directory.FullName.StartsWith(ignorePath)
+                select new DirectoryRecord(ignorePath) { Info = directory }).ToList());
+        }
+
+
+
+/*
+        public IEnumerable<DirectoryInfo> GetCheckedDirectories()
+        {
+            var q = from record in this
+                    where record.IsChecked==true
+                    select record.Info;
+            return q;
+        }
+*/
+
+/*
+        public void SetSelections(StringCollection selections)
+        {
+            IsChecked=selections.Contains(Info.FullName);                
+            foreach (var children in Directories)
+            {
+                children.SetSelections(selections);
+            }
+        }
+*/
+
+        public IEnumerator<DirectoryRecord> GetEnumerator()
+        {
+            yield return this;
+            foreach (var children in Directories)
+                foreach (var info in children)
+                {
+                    yield return info;
+                }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+}
diff --git a/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchChanges.cs b/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchChanges.cs
new file mode 100644 (file)
index 0000000..ddf0eee
--- /dev/null
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Pithos.Interfaces;
+
+namespace Pithos.Client.WPF.SelectiveSynch
+{
+    public class SelectiveSynchChanges
+    {
+        public AccountSettings Account { get; set; }
+        public string[] Added { get; set; }
+        public string[] Removed { get; set; }
+    }
+}
diff --git a/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchView.xaml b/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchView.xaml
new file mode 100644 (file)
index 0000000..645e31f
--- /dev/null
@@ -0,0 +1,47 @@
+<Window x:Class="Pithos.Client.WPF.SelectiveSynch.SelectiveSynchView"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:local="clr-namespace:Pithos.Client.WPF.SelectiveSynch" Title="Selective Synch" Height="300" Width="300" >
+    <Window.Resources>        
+        
+        <Style x:Key="TreeItemStyle" TargetType="TreeViewItem">
+            <Setter Property="IsExpanded" Value="True" />
+            <Setter Property="IsSelected" Value="{Binding IsInitiallySelected, Mode=OneTime}" />
+            <Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
+            <Setter Property="local:VirtualToggleButton.IsVirtualToggleButton" Value="True" />
+            <Setter Property="local:VirtualToggleButton.IsChecked" Value="{Binding IsChecked}" />
+        </Style>
+
+        <HierarchicalDataTemplate x:Key="CheckboxStyle" DataType="{x:Type local:DirectoryRecord}"
+                                    ItemsSource="{Binding Directories}" >
+            <StackPanel Orientation="Horizontal">
+                <CheckBox
+            Focusable="False" 
+            IsChecked="{Binding IsChecked}" 
+            VerticalAlignment="Center"
+            />
+                <ContentPresenter 
+            Content="{Binding Info.Name, Mode=OneTime}" 
+            Margin="2,0"
+            />
+            </StackPanel>
+        </HierarchicalDataTemplate>
+
+    </Window.Resources>
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+        <TreeView Grid.Row="0" Name="PithosDirectory" 
+                  ItemContainerStyle="{StaticResource TreeItemStyle}" 
+                  ItemTemplate="{StaticResource CheckboxStyle}">
+        </TreeView>
+        <StackPanel Grid.Row="1">
+            <StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Right">
+                <Button Name="SaveChanges" Content="OK" Margin="5,5,10,5" />
+                <Button Name="RejectChanges" Content="Cancel" Margin="5,5,10,5" />                
+            </StackPanel>
+        </StackPanel>
+    </Grid>
+</Window>
diff --git a/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchView.xaml.cs b/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchView.xaml.cs
new file mode 100644 (file)
index 0000000..360e547
--- /dev/null
@@ -0,0 +1,26 @@
+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.SelectiveSynch
+{
+    /// <summary>
+    /// Interaction logic for SelectiveSynchView.xaml
+    /// </summary>
+    public partial class SelectiveSynchView : Window
+    {
+        public SelectiveSynchView()
+        {
+            InitializeComponent();
+        }
+    }
+}
diff --git a/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchViewModel.cs b/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchViewModel.cs
new file mode 100644 (file)
index 0000000..ebbd4a9
--- /dev/null
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Caliburn.Micro;
+using Pithos.Client.WPF.Properties;
+using Pithos.Interfaces;
+using Pithos.Network;
+
+namespace Pithos.Client.WPF.SelectiveSynch
+{
+    class SelectiveSynchViewModel:Screen
+    {
+        private IEventAggregator _events ;
+
+        private string _rootPath;
+        private string _fragmentsPath;
+        public string RootPath
+        {
+            get { return _rootPath; }
+            set
+            {
+                _rootPath = value;
+                _fragmentsPath = Path.Combine(_rootPath, FolderConstants.FragmentsFolder);
+                _pithosDirectory = new ObservableCollection<DirectoryRecord>{
+                        new DirectoryRecord(_fragmentsPath) {Info = new DirectoryInfo(value)}};
+                NotifyOfPropertyChange(() => RootPath);
+                NotifyOfPropertyChange(()=>PithosDirectory);
+            }
+        }
+
+        private string _title;
+        public string Title
+        {
+            get { return _title; }
+            set
+            {
+                _title = value;
+                NotifyOfPropertyChange(() => Title);
+            }
+        }
+
+        public AccountSettings Account { get; set; }
+
+        private ObservableCollection<DirectoryRecord> _pithosDirectory;
+        public ObservableCollection<DirectoryRecord> PithosDirectory
+        {
+            get { return _pithosDirectory; }
+        }
+
+        private ObservableCollection<DirectoryInfo> _checks;
+        public ObservableCollection<DirectoryInfo> Checks
+        {
+            get { return _checks; }
+        }
+
+        public void GetChecks()
+        {
+            var root = PithosDirectory[0];            
+            _checks = new ObservableCollection<DirectoryInfo>(
+                from record in root
+                where record.IsChecked==true
+                select record.Info);
+            NotifyOfPropertyChange(() => Checks);
+        }
+
+        public SelectiveSynchViewModel(IEventAggregator events, AccountSettings account)
+        {
+            Account = account;
+            AccountName = account.AccountName;
+            Title = account.AccountName;
+            RootPath = account.RootPath;
+
+            SetInitialSelections(account);
+        }
+
+        private void SetInitialSelections(AccountSettings account)
+        {
+            var selections = account.SelectiveFolders;
+            if (selections.Count == 0)
+                return;
+            var root = PithosDirectory[0];
+            var selects= from record in root
+                             where selections.Contains(record.Info.FullName)
+                             select record;
+            selects.Apply(record=>record.IsChecked=true);            
+        }
+
+        protected string AccountName { get; set; }
+
+        public void SaveChanges()
+        {
+            var selections = GetSelectedFolderNames();
+
+            SaveSettings(selections);
+            var root = PithosDirectory[0];
+            
+            var added= (from record in root
+                         where record.Added
+                         select record.Info.FullName.ToLower()).ToArray();
+            var removed= (from record in root
+                         where record.Removed
+                         select record.Info.FullName.ToLower()).ToArray();            
+
+            _events.Publish(new SelectiveSynchChanges{Account=Account,Added=added,Removed=removed});
+            
+
+            
+
+            TryClose(true);
+        }
+
+        
+        private void SaveSettings(string[] selections)
+        {
+            Account.SelectiveFolders.Clear();
+            Account.SelectiveFolders.AddRange(selections);
+            Settings.Default.Save();            
+        }
+
+        private string[] GetSelectedFolderNames()
+        {
+            var root = PithosDirectory[0];
+            var selections = from record in root
+                         where record.IsChecked == true
+                         select record.Info.FullName;
+            return selections.ToArray();
+        }
+
+        public void RejectChanges()
+        {
+            TryClose(false);
+        }
+    }
+}
diff --git a/trunk/Pithos.Client.WPF/SelectiveSynch/VirtualToggleButton.cs b/trunk/Pithos.Client.WPF/SelectiveSynch/VirtualToggleButton.cs
new file mode 100644 (file)
index 0000000..ca3e543
--- /dev/null
@@ -0,0 +1,270 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls.Primitives;
+using System.Windows.Input;
+
+namespace Pithos.Client.WPF.SelectiveSynch
+{
+    public static class VirtualToggleButton
+    {
+        #region attached properties
+
+        #region IsChecked
+
+        /// <summary>
+        /// IsChecked Attached Dependency Property
+        /// </summary>
+        public static readonly DependencyProperty IsCheckedProperty =
+            DependencyProperty.RegisterAttached("IsChecked", typeof(Nullable<bool>), typeof(VirtualToggleButton),
+                new FrameworkPropertyMetadata((Nullable<bool>)false,
+                    FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal,
+                    new PropertyChangedCallback(OnIsCheckedChanged)));
+
+        /// <summary>
+        /// Gets the IsChecked property.  This dependency property 
+        /// indicates whether the toggle button is checked.
+        /// </summary>
+        public static Nullable<bool> GetIsChecked(DependencyObject d)
+        {
+            return (Nullable<bool>)d.GetValue(IsCheckedProperty);
+        }
+
+        /// <summary>
+        /// Sets the IsChecked property.  This dependency property 
+        /// indicates whether the toggle button is checked.
+        /// </summary>
+        public static void SetIsChecked(DependencyObject d, Nullable<bool> value)
+        {
+            d.SetValue(IsCheckedProperty, value);
+        }
+
+        /// <summary>
+        /// Handles changes to the IsChecked property.
+        /// </summary>
+        private static void OnIsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            UIElement pseudobutton = d as UIElement;
+            if (pseudobutton != null)
+            {
+                Nullable<bool> newValue = (Nullable<bool>)e.NewValue;
+                if (newValue == true)
+                {
+                    RaiseCheckedEvent(pseudobutton);
+                }
+                else if (newValue == false)
+                {
+                    RaiseUncheckedEvent(pseudobutton);
+                }
+                else
+                {
+                    RaiseIndeterminateEvent(pseudobutton);
+                }
+            }
+        }
+
+        #endregion
+
+        #region IsThreeState
+
+        /// <summary>
+        /// IsThreeState Attached Dependency Property
+        /// </summary>
+        public static readonly DependencyProperty IsThreeStateProperty =
+            DependencyProperty.RegisterAttached("IsThreeState", typeof(bool), typeof(VirtualToggleButton),
+                new FrameworkPropertyMetadata((bool)false));
+
+        /// <summary>
+        /// Gets the IsThreeState property.  This dependency property 
+        /// indicates whether the control supports two or three states.  
+        /// IsChecked can be set to null as a third state when IsThreeState is true.
+        /// </summary>
+        public static bool GetIsThreeState(DependencyObject d)
+        {
+            return (bool)d.GetValue(IsThreeStateProperty);
+        }
+
+        /// <summary>
+        /// Sets the IsThreeState property.  This dependency property 
+        /// indicates whether the control supports two or three states. 
+        /// IsChecked can be set to null as a third state when IsThreeState is true.
+        /// </summary>
+        public static void SetIsThreeState(DependencyObject d, bool value)
+        {
+            d.SetValue(IsThreeStateProperty, value);
+        }
+
+        #endregion
+
+        #region IsVirtualToggleButton
+
+        /// <summary>
+        /// IsVirtualToggleButton Attached Dependency Property
+        /// </summary>
+        public static readonly DependencyProperty IsVirtualToggleButtonProperty =
+            DependencyProperty.RegisterAttached("IsVirtualToggleButton", typeof(bool), typeof(VirtualToggleButton),
+                new FrameworkPropertyMetadata((bool)false,
+                    new PropertyChangedCallback(OnIsVirtualToggleButtonChanged)));
+
+        /// <summary>
+        /// Gets the IsVirtualToggleButton property.  This dependency property 
+        /// indicates whether the object to which the property is attached is treated as a VirtualToggleButton.  
+        /// If true, the object will respond to keyboard and mouse input the same way a ToggleButton would.
+        /// </summary>
+        public static bool GetIsVirtualToggleButton(DependencyObject d)
+        {
+            return (bool)d.GetValue(IsVirtualToggleButtonProperty);
+        }
+
+        /// <summary>
+        /// Sets the IsVirtualToggleButton property.  This dependency property 
+        /// indicates whether the object to which the property is attached is treated as a VirtualToggleButton.  
+        /// If true, the object will respond to keyboard and mouse input the same way a ToggleButton would.
+        /// </summary>
+        public static void SetIsVirtualToggleButton(DependencyObject d, bool value)
+        {
+            d.SetValue(IsVirtualToggleButtonProperty, value);
+        }
+
+        /// <summary>
+        /// Handles changes to the IsVirtualToggleButton property.
+        /// </summary>
+        private static void OnIsVirtualToggleButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            IInputElement element = d as IInputElement;
+            if (element != null)
+            {
+                if ((bool)e.NewValue)
+                {
+                    element.MouseLeftButtonDown += OnMouseLeftButtonDown;
+                    element.KeyDown += OnKeyDown;
+                }
+                else
+                {
+                    element.MouseLeftButtonDown -= OnMouseLeftButtonDown;
+                    element.KeyDown -= OnKeyDown;
+                }
+            }
+        }
+
+        #endregion
+
+        #endregion
+
+        #region routed events
+
+        #region Checked
+
+        /// <summary>
+        /// A static helper method to raise the Checked event on a target element.
+        /// </summary>
+        /// <param name="target">UIElement or ContentElement on which to raise the event</param>
+        internal static RoutedEventArgs RaiseCheckedEvent(UIElement target)
+        {
+            if (target == null) return null;
+
+            RoutedEventArgs args = new RoutedEventArgs();
+            args.RoutedEvent = ToggleButton.CheckedEvent;
+            RaiseEvent(target, args);
+            return args;
+        }
+
+        #endregion
+
+        #region Unchecked
+
+        /// <summary>
+        /// A static helper method to raise the Unchecked event on a target element.
+        /// </summary>
+        /// <param name="target">UIElement or ContentElement on which to raise the event</param>
+        internal static RoutedEventArgs RaiseUncheckedEvent(UIElement target)
+        {
+            if (target == null) return null;
+
+            RoutedEventArgs args = new RoutedEventArgs();
+            args.RoutedEvent = ToggleButton.UncheckedEvent;
+            RaiseEvent(target, args);
+            return args;
+        }
+
+        #endregion
+
+        #region Indeterminate
+
+        /// <summary>
+        /// A static helper method to raise the Indeterminate event on a target element.
+        /// </summary>
+        /// <param name="target">UIElement or ContentElement on which to raise the event</param>
+        internal static RoutedEventArgs RaiseIndeterminateEvent(UIElement target)
+        {
+            if (target == null) return null;
+
+            RoutedEventArgs args = new RoutedEventArgs();
+            args.RoutedEvent = ToggleButton.IndeterminateEvent;
+            RaiseEvent(target, args);
+            return args;
+        }
+
+        #endregion
+
+        #endregion
+
+        #region private methods
+
+        private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+        {
+            e.Handled = true;
+            UpdateIsChecked(sender as DependencyObject);
+        }
+
+        private static void OnKeyDown(object sender, KeyEventArgs e)
+        {
+            if (e.OriginalSource == sender)
+            {
+                if (e.Key == Key.Space)
+                {
+                    // ignore alt+space which invokes the system menu
+                    if ((Keyboard.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt) return;
+
+                    UpdateIsChecked(sender as DependencyObject);
+                    e.Handled = true;
+
+                }
+                else if (e.Key == Key.Enter && (bool)(sender as DependencyObject).GetValue(KeyboardNavigation.AcceptsReturnProperty))
+                {
+                    UpdateIsChecked(sender as DependencyObject);
+                    e.Handled = true;
+                }
+            }
+        }
+
+        private static void UpdateIsChecked(DependencyObject d)
+        {
+            Nullable<bool> isChecked = GetIsChecked(d);
+            if (isChecked == true)
+            {
+                SetIsChecked(d, GetIsThreeState(d) ? (Nullable<bool>)null : (Nullable<bool>)false);
+            }
+            else
+            {
+                SetIsChecked(d, isChecked.HasValue);
+            }
+        }
+
+        private static void RaiseEvent(DependencyObject target, RoutedEventArgs args)
+        {
+            if (target is UIElement)
+            {
+                (target as UIElement).RaiseEvent(args);
+            }
+            else if (target is ContentElement)
+            {
+                (target as ContentElement).RaiseEvent(args);
+            }
+        }
+
+        #endregion
+    }
+}
index 801aba9..d06c495 100644 (file)
@@ -13,6 +13,7 @@ using Caliburn.Micro;
 using Hardcodet.Wpf.TaskbarNotification;
 using Pithos.Client.WPF.Configuration;
 using Pithos.Client.WPF.Properties;
+using Pithos.Client.WPF.SelectiveSynch;
 using Pithos.Core;
 using Pithos.Interfaces;
 using System;
@@ -25,7 +26,8 @@ namespace Pithos.Client.WPF {
     using System.ComponentModel.Composition;
 
     [Export(typeof(IShell))]
-    public class ShellViewModel : ViewAware, IStatusNotification, IShell, IHandle<Notification>
+    public class ShellViewModel : ViewAware, IStatusNotification, IShell, 
+        IHandle<Notification>, IHandle<SelectiveSynchChanges>
     {
        
         private IStatusChecker _statusChecker;
@@ -52,7 +54,9 @@ namespace Pithos.Client.WPF {
             _windowManager = windowManager;
             OpenPithosFolderCommand = new PithosCommand(OpenPithosFolder);
             _statusChecker = statusChecker;
-            _events = events;            
+            _events = events;
+            _events.Subscribe(this);
+
             Settings = settings;
                                    
             UsageMessage = "Using 15% of 50 GB";
@@ -188,8 +192,9 @@ namespace Pithos.Client.WPF {
         public void ShowPreferences()
         {
             Settings.Reload();
-            var preferences = new PreferencesViewModel(_events, this,Settings);            
+            var preferences = new PreferencesViewModel(_windowManager,_events, this,Settings);            
             _windowManager.ShowDialog(preferences);
+            
         }
 
 
@@ -206,7 +211,7 @@ namespace Pithos.Client.WPF {
             if (activeAccount == null)
                 return;            
 
-            var site = String.Format("http://{0}/ui/?token={1}&user={2}",
+            var site = String.Format("{0}/ui/?token={1}&user={2}",
                 Properties.Settings.Default.PithosSite,activeAccount.ApiKey,
                 activeAccount.AccountName);
             Process.Start(site);
@@ -336,10 +341,11 @@ namespace Pithos.Client.WPF {
                     _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
                     return;
                 }
-                var credentials = t.Result;
+                var credentials = t.Result;                
                 var account =Settings.Accounts.FirstOrDefault(act => act.AccountName == credentials.UserName);
                 account.ApiKey = credentials.Password;
                 monitor.ApiKey = credentials.Password;
+                Settings.Save();
                 Task.Factory.StartNewDelayed(10000, () => StartMonitor(monitor,retries+1));
             });
         }
@@ -384,32 +390,6 @@ namespace Pithos.Client.WPF {
         }
 
 
-        public void Handle(Notification notification)
-        {
-            if (!Settings.ShowDesktopNotifications)
-                return;
-            BalloonIcon icon = BalloonIcon.None;
-            switch (notification.Level)
-            {
-                case TraceLevel.Error:
-                    icon = BalloonIcon.Error;
-                    break;
-                case TraceLevel.Info:
-                case TraceLevel.Verbose:
-                    icon = BalloonIcon.Info;
-                    break;
-                case TraceLevel.Warning:
-                    icon = BalloonIcon.Warning;
-                    break;
-                default:
-                    icon = BalloonIcon.None;
-                    break;
-            }
-
-            var tv = (ShellView)this.GetView();
-            tv.TaskbarView.ShowBalloonTip(notification.Title, notification.Message, icon);
-        }
-
         public void RemoveMonitor(string accountName)
         {
             if (String.IsNullOrWhiteSpace(accountName))
@@ -488,5 +468,47 @@ namespace Pithos.Client.WPF {
             _statusService = null;
 
         }
+        #region Event Handlers
+        
+        public void Handle(SelectiveSynchChanges message)
+        {
+            var accountName = message.Account.AccountName;
+            PithosMonitor monitor;
+            if (_monitors.TryGetValue(accountName, out monitor))
+            {
+                monitor.AddSelectivePaths(message.Added);
+                monitor.RemoveSelectivePaths(message.Removed);
+
+            }
+            
+        }
+
+
+        public void Handle(Notification notification)
+        {
+            if (!Settings.ShowDesktopNotifications)
+                return;
+            BalloonIcon icon = BalloonIcon.None;
+            switch (notification.Level)
+            {
+                case TraceLevel.Error:
+                    icon = BalloonIcon.Error;
+                    break;
+                case TraceLevel.Info:
+                case TraceLevel.Verbose:
+                    icon = BalloonIcon.Info;
+                    break;
+                case TraceLevel.Warning:
+                    icon = BalloonIcon.Warning;
+                    break;
+                default:
+                    icon = BalloonIcon.None;
+                    break;
+            }
+
+            var tv = (ShellView)this.GetView();
+            tv.TaskbarView.ShowBalloonTip(notification.Title, notification.Message, icon);
+        }
+        #endregion
     }
 }
index ba323a3..d70d372 100644 (file)
@@ -134,6 +134,14 @@ namespace Pithos.Core.Agents
 
         public string FragmentsPath { get; set; }
 
+        private List<string> _selectivePaths = new List<string>();
+        public List<string> SelectivePaths
+        {
+            get { return _selectivePaths; }
+            set { _selectivePaths = value; }
+        }
+
+
         public void Post(WorkflowState workflowState)
         {
             if (workflowState == null)
index 37d8b6d..aa087af 100644 (file)
@@ -98,7 +98,7 @@ namespace Pithos.Core.Agents
                             break;
                         case CloudActionType.DownloadUnconditional:
 
-                            DownloadCloudFile(accountInfo, account, container, new Uri(cloudFile.Name, UriKind.Relative),
+                            DownloadCloudFile(accountInfo, account, container, cloudFile,
                                               downloadPath);
                             break;
                         case CloudActionType.DeleteCloud:
@@ -112,9 +112,8 @@ namespace Pithos.Core.Agents
                         case CloudActionType.MustSynch:
 
                             if (!File.Exists(downloadPath))
-                            {
-                                var cloudUri = new Uri(action.CloudFile.Name, UriKind.Relative);
-                                DownloadCloudFile(accountInfo, account, container, cloudUri, downloadPath);
+                            {                                
+                                DownloadCloudFile(accountInfo, account, container, cloudFile, downloadPath);
                             }
                             else
                             {
@@ -202,7 +201,7 @@ namespace Pithos.Core.Agents
                 {
                     case FileStatus.Unchanged:                        
                         //If the local file's status is Unchanged, we can go on and download the newer cloud file
-                        DownloadCloudFile(accountInfo,account, container,cloudUri,downloadPath);
+                        DownloadCloudFile(accountInfo,account, container,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
@@ -360,7 +359,7 @@ namespace Pithos.Core.Agents
                                 client.ListObjects(accountInfo.UserName, FolderConstants.TrashContainer, since));
 
                 var listShared = Task<IList<ObjectInfo>>.Factory.StartNew(() =>
-                                client.ListSharedObjects(since));
+                                client.ListSharedObjects());
 
                 var listAll = Task.Factory.TrackedSequence(
                     () => listObjects,
@@ -552,7 +551,7 @@ namespace Pithos.Core.Agents
         }
 
         //Download a file.
-        private void DownloadCloudFile(AccountInfo accountInfo, string account, string container, Uri relativeUrl, string localPath)
+        private void DownloadCloudFile(AccountInfo accountInfo, string account, string container,ObjectInfo cloudFile , string localPath)
         {
             if (accountInfo == null)
                 throw new ArgumentNullException("accountInfo");
@@ -560,19 +559,22 @@ namespace Pithos.Core.Agents
                 throw new ArgumentNullException("account");
             if (String.IsNullOrWhiteSpace(container))
                 throw new ArgumentNullException("container");
-            if (relativeUrl == null)
-                throw new ArgumentNullException("relativeUrl");
+            if (cloudFile == null)
+                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();
             
-            var download=Task.Factory.Iterate(DownloadIterator(accountInfo,account,container, relativeUrl, localPath));
+            Debug.Assert(cloudFile.Account==account);
+            Debug.Assert(cloudFile.Container == container);
+
+            var download=Task.Factory.Iterate(DownloadIterator(accountInfo,account,container, cloudFile, localPath));
             download.Wait();
         }
 
-        private IEnumerable<Task> DownloadIterator(AccountInfo accountInfo, string account, string container, Uri relativeUrl, string localPath)
+        private IEnumerable<Task> DownloadIterator(AccountInfo accountInfo, string account, string container, ObjectInfo cloudFile, string localPath)
         {
             if (accountInfo == null)
                 throw new ArgumentNullException("accountInfo");
@@ -580,16 +582,18 @@ namespace Pithos.Core.Agents
                 throw new ArgumentNullException("account");
             if (String.IsNullOrWhiteSpace(container))
                 throw new ArgumentNullException("container");
-            if (relativeUrl==null)
-                throw new ArgumentNullException("relativeUrl");
+            if (cloudFile==null)
+                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 (url.EndsWith(".ignore",StringComparison.InvariantCultureIgnoreCase))
+            if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))
                 yield break;
 
             //Are we already downloading or uploading the file? 
@@ -609,15 +613,20 @@ namespace Pithos.Core.Agents
                 //If it's a small file
                 var downloadTask=(serverHash.Hashes.Count == 1 )
                     //Download it in one go
-                    ? DownloadEntireFile(accountInfo,client, account, container, relativeUrl, localPath) 
+                    ? DownloadEntireFile(accountInfo,client, account, container, relativeUrl, localPath,serverHash) 
                     //Otherwise download it block by block
                     : DownloadWithBlocks(accountInfo,client, account, container, relativeUrl, localPath, serverHash);
 
                 yield return downloadTask;
 
-
+                if (cloudFile.AllowedTo == "read")
+                {
+                    var attributes=File.GetAttributes(localPath);
+                    File.SetAttributes(localPath,attributes|FileAttributes.ReadOnly);
+                }
                 //Retrieve the object's metadata
                 var info=client.GetObjectInfo(account, container, url);
+                Debug.Assert(cloudFile==info);
                 //And store it
                 StatusKeeper.StoreInfo(localPath, info);
                 
@@ -628,7 +637,7 @@ namespace Pithos.Core.Agents
         }
 
         //Download a small file with a single GET operation
-        private Task DownloadEntireFile(AccountInfo accountInfo, CloudFilesClient client, string account, string container, Uri relativeUrl, string localPath)
+        private Task DownloadEntireFile(AccountInfo accountInfo, CloudFilesClient client, string account, string container, Uri relativeUrl, string localPath,TreeHash serverHash)
         {
             if (client == null)
                 throw new ArgumentNullException("client");
@@ -644,20 +653,39 @@ namespace Pithos.Core.Agents
                 throw new ArgumentException("The localPath must be rooted", "localPath");
             Contract.EndContractBlock();
 
+            //If the file already exists
+            if (File.Exists(localPath))
+            {
+                //First check with MD5 as this is a small file
+                var localMD5 = Signature.CalculateMD5(localPath);
+                var cloudHash=serverHash.TopHash.ToHashString();
+                if (localMD5==cloudHash)
+                    return CompletedTask.Default;
+                //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;
+            }
+
             var fileAgent = GetFileAgent(accountInfo);
             //Calculate the relative file path for the new file
             var relativePath = relativeUrl.RelativeUriToFilePath();
             //The file will be stored in a temporary location while downloading with an extension .download
             var tempPath = Path.Combine(fileAgent.FragmentsPath, relativePath + ".download");
             //Make sure the target folder exists. DownloadFileTask will not create the folder
-            var directoryPath = Path.GetDirectoryName(tempPath);
-            if (!Directory.Exists(directoryPath))
-                Directory.CreateDirectory(directoryPath);
+            var tempFolder = Path.GetDirectoryName(tempPath);
+            if (!Directory.Exists(tempFolder))
+                Directory.CreateDirectory(tempFolder);
 
             //Download the object to the temporary location
             var getObject = client.GetObject(account, container, relativeUrl.ToString(), tempPath).ContinueWith(t =>
             {
                 t.PropagateExceptions();
+                //Create the local folder if it doesn't exist (necessary for shared objects)
+                var localFolder = Path.GetDirectoryName(localPath);
+                if (!Directory.Exists(localFolder))
+                    Directory.CreateDirectory(localFolder);
                 //And move it to its actual location once downloading is finished
                 if (File.Exists(localPath))
                     File.Replace(tempPath,localPath,null,true);
@@ -821,6 +849,9 @@ namespace Pithos.Core.Agents
                     yield break;
                 }
 
+                if (info.AllowedTo=="read")
+                    yield break;
+
                 //Mark the file as modified while we upload it
                 StatusKeeper.SetFileOverlayStatus(fullFileName, FileOverlayStatus.Modified);
                 //And then upload it
index ec33a0d..e245deb 100644 (file)
@@ -239,7 +239,6 @@ namespace Pithos.Core
         public bool UsePithos { get; set; }
 
 
-
         internal class LocalFileComparer:EqualityComparer<CloudAction>
         {
             public override bool Equals(CloudAction x, CloudAction y)
@@ -370,6 +369,21 @@ namespace Pithos.Core
 
             StatusKeeper.ChangeRoots(oldPath, newPath);
         }
+
+        public void AddSelectivePaths(string[] added)
+        {
+           /* FileAgent.SelectivePaths.AddRange(added);
+            NetworkAgent.SyncPaths(added);*/
+        }
+
+        public void RemoveSelectivePaths(string[] removed)
+        {
+            FileAgent.SelectivePaths.RemoveAll(removed.Contains);
+            foreach (var removedPath in removed.Where(Directory.Exists))
+            {
+                Directory.Delete(removedPath,true);
+            }
+        }
     }
 
     public interface IStatusNotification
index 562b1d8..70c232d 100644 (file)
@@ -253,10 +253,14 @@ namespace Pithos.Network
                 var accounts = ListSharingAccounts(since);
                 foreach (var account in accounts)
                 {
+                    //Skip the account if it hasn't been modified
+                    if (account.last_modified < since)
+                        continue;
+
                     var containers = ListContainers(account.name);
                     foreach (var container in containers)
                     {
-                        var containerObjects = ListObjects(account.name, container.Name, account.last_modified);
+                        var containerObjects = ListObjects(account.name, container.Name, since);
                         objects.AddRange(containerObjects);
                     }
                 }