Tested multi-user and shared objects.
<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.
</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>
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;
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;
}
-/*
- 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;
#region Commands
-
+ public void SelectiveSyncFolders()
+ {
+ var model = new SelectiveSynchViewModel(_events,CurrentAccount);
+ if (_windowManager.ShowDialog(model) == true)
+ {
+
+ }
+ }
public void SaveChanges()
{
}
private AccountSettings _currentAccount;
+ private IWindowManager _windowManager;
+
public AccountSettings CurrentAccount
{
get {
--- /dev/null
+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();
+ }
+ }
+}
--- /dev/null
+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; }
+ }
+}
--- /dev/null
+<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>
--- /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.SelectiveSynch
+{
+ /// <summary>
+ /// Interaction logic for SelectiveSynchView.xaml
+ /// </summary>
+ public partial class SelectiveSynchView : Window
+ {
+ public SelectiveSynchView()
+ {
+ InitializeComponent();
+ }
+ }
+}
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+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
+ }
+}
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;
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;
_windowManager = windowManager;
OpenPithosFolderCommand = new PithosCommand(OpenPithosFolder);
_statusChecker = statusChecker;
- _events = events;
+ _events = events;
+ _events.Subscribe(this);
+
Settings = settings;
UsageMessage = "Using 15% of 50 GB";
public void ShowPreferences()
{
Settings.Reload();
- var preferences = new PreferencesViewModel(_events, this,Settings);
+ var preferences = new PreferencesViewModel(_windowManager,_events, this,Settings);
_windowManager.ShowDialog(preferences);
+
}
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);
_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));
});
}
}
- 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))
_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
}
}
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)
break;
case CloudActionType.DownloadUnconditional:
- DownloadCloudFile(accountInfo, account, container, new Uri(cloudFile.Name, UriKind.Relative),
+ DownloadCloudFile(accountInfo, account, container, cloudFile,
downloadPath);
break;
case CloudActionType.DeleteCloud:
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
{
{
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
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,
}
//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");
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");
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?
//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);
}
//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");
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);
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
public bool UsePithos { get; set; }
-
internal class LocalFileComparer:EqualityComparer<CloudAction>
{
public override bool Equals(CloudAction x, CloudAction y)
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
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);
}
}