Revision 69588a95

b/trunk/Pithos.Client.WPF.Test/NodeTest.cs
3 3
using System.Linq;
4 4
using System.Text;
5 5
using NUnit.Framework;
6
using Pithos.Client.WPF.SelectiveSynch;
6 7
using Pithos.Client.WPF.Utils;
8
using Pithos.Interfaces;
7 9

  
8 10
namespace Pithos.Client.WPF.Test
9 11
{
......
120 122
                            };
121 123

  
122 124
            Assert.That(source.ToTree(s=>s.Item1,s=>s.Item2).First().Equals(target), Is.True);
125
        } [Test]
126
        
127
        public void TestObjectInfoToTree()
128
        {
129
            var target = new DirectoryRecord{ DisplayName= "Root",
130
                                Directories =
131
                                    {
132
                                        new DirectoryRecord{DisplayName = "Root/DisplayName1",
133
                                            Directories =
134
                                                {
135
                                                    new DirectoryRecord{DisplayName="Root/DisplayName1/File2"},
136
                                                    new DirectoryRecord{DisplayName="Root/DisplayName1/DisplayName11",
137
                                                        Directories=
138
                                                            {
139
                                                                new DirectoryRecord{DisplayName="Root/DisplayName1/DisplayName11/DisplayName111",
140
                                                                Directories=
141
                                                                    {
142
                                                                        new DirectoryRecord{DisplayName="Root/DisplayName1/DisplayName11/DisplayName111/File1"}        
143
                                                                    }}
144
                                                            }
145
                                                    },
146
                                                }
147
                                        },                                        
148
                                    }
149
                            };
150
            var account = "someaccount";
151
            var container = "Root";
152
            var source= new[]
153
                            {
154
                                new ObjectInfo{Account=account,Container=container,Name="Path1",Content_Type="application/directory"},
155
                                new ObjectInfo{Account=account,Container=container,Name="Path1/Path11",Content_Type="application/folder"},
156
                                new ObjectInfo{Account=account,Container=container,Name="Path1/Path11/Path111"},
157
                                new ObjectInfo{Account=account,Container=container,Name="Path1/Path11/Path111/File1",Content_Type="application/octet-stream"},
158
                                new ObjectInfo{Account=account,Container=container,Name="Path1/File2"},
159
                                new ObjectInfo{Account=account,Container=container,Name="Path2/File2"},
160
                                new ObjectInfo{Account=account,Container=container,Name="Path2/Path21/File2"},
161
                                new ObjectInfo{Account=account,Container=container,Name="File02"},
162
                                new ObjectInfo{Account=account,Container=container,Name="File03"}
163
                            };
164

  
165
            var tree = source.ToTree();
166
            var allNodes = (from DirectoryRecord root in tree
167
                            from DirectoryRecord record in root
168
                            select record).ToList();    
169
            Assert.That(allNodes.Count,Is.EqualTo(5));
123 170
        }
124 171
    }
125 172
}
b/trunk/Pithos.Client.WPF.Test/Pithos.Client.WPF.Test.csproj
61 61
      <Project>{4D9406A3-50ED-4672-BB97-A0B3EA4946FE}</Project>
62 62
      <Name>Pithos.Client.WPF</Name>
63 63
    </ProjectReference>
64
    <ProjectReference Include="..\Pithos.Interfaces\Pithos.Interfaces.csproj">
65
      <Project>{7EEFF32F-CCF8-436A-9E0B-F40434C09AF4}</Project>
66
      <Name>Pithos.Interfaces</Name>
67
    </ProjectReference>
64 68
  </ItemGroup>
65 69
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
66 70
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
b/trunk/Pithos.Client.WPF/FileProperties/ConflictsView.xaml
3 3
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:cal="http://www.caliburnproject.org" 
4 4
        xmlns:cnv="clr-namespace:Pithos.Client.WPF.Converters"
5 5
        xmlns:model="clr-namespace:Pithos.Client.WPF.FileProperties"
6
        Title="Conflicts" Height="500" Width="500" x:Name="This"
7
		>
6
        Title="Conflicts" Height="500" Width="500" x:Name="This" xmlns:my="clr-namespace:Microsoft.Windows.Controls.Core.Converters;assembly=WPFToolkit.Extended" Icon="/PithosPlus;component/Images/Pithos.ico">
8 7
    <Window.Resources>
9 8
        <ResourceDictionary>
10 9
            <ContextMenu  x:Key="RowMenu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
......
13 12
        <Style x:Key="DefaultRowStyle" TargetType="{x:Type DataGridRow}">
14 13
            <Setter Property="ContextMenu" Value="{StaticResource RowMenu}" />
15 14
        </Style>
16
                <ResourceDictionary.MergedDictionaries>
15
            <BooleanToVisibilityConverter x:Key="BoolToVisible" />
16
            <my:InverseBoolConverter x:Key="BoolToInvisible" />
17
            <ResourceDictionary.MergedDictionaries>
17 18
                    <ResourceDictionary Source="..\PithosStyles.xaml" />
18 19
                </ResourceDictionary.MergedDictionaries>
19 20
            </ResourceDictionary>
......
24 25
            <RowDefinition Height="Auto"/>
25 26
        </Grid.RowDefinitions>
26 27
        
27
        <DataGrid x:Name="Conflicts" HorizontalContentAlignment="Stretch" Grid.Row="0" AutoGenerateColumns="false" RowStyle="{StaticResource DefaultRowStyle}">
28
        <DataGrid x:Name="Conflicts" HorizontalContentAlignment="Stretch" Grid.Row="0" 
29
                  AutoGenerateColumns="false" 
30
                  RowStyle="{StaticResource DefaultRowStyle}"
31
                  Visibility="{Binding Converter={StaticResource BoolToVisible}, Path=HasConflicts}"
32
                  >
28 33
            <DataGrid.Columns>
29 34
                <DataGridTextColumn x:Name="FilePath" Binding="{Binding FilePath}" Header="File" Width="*" />
30 35
                <DataGridTextColumn x:Name="LocalModified" Binding="{Binding LocalModified}" Header="Local Date" Width="Auto" />
......
43 48
                </DataGridTemplateColumn>
44 49
            </DataGrid.Columns>
45 50
        </DataGrid>
51
        <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" TextAlignment="Center" Visibility="{Binding Converter={StaticResource BoolToInvisible}, Path=HasConflicts}" FontWeight="Bold">There are no conflicts.</TextBlock>
46 52
        <StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Right">
47 53
            <Button Name="Apply" Content="OK" Margin="5,5,10,5" Style="{StaticResource ButtonStyle}" IsDefault="False" />
48 54
            <Button Name="Cancel" Content="Cancel" Margin="5,5,10,5" Style="{StaticResource ButtonStyle}" IsCancel="True" />
b/trunk/Pithos.Client.WPF/FileProperties/ConflictsViewModel.cs
9 9
using System.Text;
10 10
using Caliburn.Micro;
11 11
using Pithos.Client.WPF.Converters;
12
using Pithos.Client.WPF.Utils;
12 13
using Pithos.Core;
13 14
using Pithos.Interfaces;
14 15

  
......
45 46
        private string _reason;
46 47
        public string Reason
47 48
        {
48
            get { return _reason; }
49
            get
50
            {
51
                return _reason;
52
            }
49 53
            set
50 54
            {
51 55
                _reason = value;
......
91 95
            get { return _conflicts; }
92 96
        }
93 97

  
98
        public bool HasConflicts
99
        {
100
            get { return Conflicts!=null && Conflicts.Count > 0; }
101
        }
102

  
94 103
        public string[]  Actions
95 104
        {
96 105
            get { return new[] {"Keep Local", "Keep Server", "Keep Both"}; }
......
105 114
                         select state;
106 115
            var conflicts = from state in fileStates
107 116
                            let info=FileInfoExtensions.FromPath(state.FilePath)
108
                            select new ConflictFile {FilePath = state.FilePath,Reason=state.ConflictReason,LocalModified = info.LastWriteTime};          
117
                            select new ConflictFile
118
                                       {
119
                                           FilePath = state.FilePath,
120
                                           Reason=state.ConflictReason??state.FileStatus.Name() ,
121
                                           LocalModified = info.LastWriteTime
122
                                       };          
109 123
            _conflicts = new ObservableCollection<ConflictFile>(conflicts.ToList());
110 124
            
111 125
        }
b/trunk/Pithos.Client.WPF/Preferences/PreferencesViewModel.cs
488 488

  
489 489
                var cachePath = Path.Combine(CurrentAccount.RootPath, FolderConstants.CacheFolder);
490 490
                var dir = new DirectoryInfo(cachePath);
491
                //The file may not exist if we just created the account
492
                if (!dir.Exists)
493
                    return;
491 494
                dir.EnumerateFiles().Apply(file=>file.Delete());
492 495
                dir.EnumerateDirectories().Apply(folder => folder.Delete(true));
493 496
            }
b/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchViewModel.cs
109 109
                                  {
110 110
                                      DisplayName = container.Name,
111 111
                                      Uri=new Uri(client.StorageUrl,String.Format(@"{0}/{1}",Account.AccountName, container.Name)),
112
                                      Directories = (from dir in client.ListObjects(AccountName, container.Name)                                                     
113
                                                     where dir.IsDirectory
112
                                      Directories = (from dir in client.ListObjects(AccountName, container.Name)                                                                                                          
114 113
                                                     select dir).ToTree()
115 114
                                  };
116 115
            var ownFolders = dirs.ToList();
......
125 124
                                                        {
126 125
                                                            DisplayName=container.Name,
127 126
                                                            Uri = new Uri(client.StorageUrl, "../" + account.name + "/" + container.Name),
128
                                                            Directories=(from folder in client.ListObjects(account.name,container.Name)
129
                                                                        where folder.IsDirectory
127
                                                            Directories=(from folder in client.ListObjects(account.name,container.Name)                                                                        
130 128
                                                                        select folder).ToTree()
131 129
                                                        }).ToList()
132 130
                             };                                                          
b/trunk/Pithos.Client.WPF/Utils/EnumerableExtensions.cs
4 4
using System.Linq;
5 5
using System.Linq.Expressions;
6 6
using System.Text;
7
using System.Text.RegularExpressions;
7 8
using Pithos.Client.WPF.SelectiveSynch;
9
using Pithos.Core;
8 10
using Pithos.Interfaces;
9 11

  
10 12
namespace Pithos.Client.WPF.Utils
......
79 81

  
80 82
        public static List<DirectoryRecord> ToTree(this IEnumerable<ObjectInfo> enumerable)
81 83
        {
84
            //Order the items to ensure that children always come after their parents
82 85
            var orderedItems=enumerable.OrderBy(o=>o.Uri.ToString());
86
            //Each item is stored in lookups
83 87
            var lookups = new Dictionary<string,DirectoryRecord>();
84
            var nodes = new List<DirectoryRecord>();
88
            
89
            //RootNodes contains only the root nodes
90
            var rootNodes = new List<DirectoryRecord>();            
91

  
85 92
            foreach (var item in orderedItems)
86 93
            {
87 94
                var path = item.Uri.ToString();
88
                var newNode = new DirectoryRecord{ DisplayName=item.Name.Split('/').Last(),ObjectInfo=item};
89
                lookups[path] = newNode;
90

  
91
                var lastIndex = path.LastIndexOf("/", StringComparison.Ordinal);
92
                var upTo = lastIndex < 0 ? path.Length - 1 : lastIndex;
93
                var parentPath = path.Substring(0, upTo);              
94
  
95
                //Calculate the parent path
96
                var parentPath = GetParentPath(path);
97
                var parentName = GetParentPath(item.Name);
95 98
                DirectoryRecord parent;
96
                if (lookups.TryGetValue(parentPath, out parent))
99
                DirectoryRecord newNode;
100

  
101
                //Dont't add files
102
                if (!item.IsDirectory)
97 103
                {
98
                    parent.Directories.Add(newNode);   
99
                    parent.Directories.Sort((x,y)=>String.CompareOrdinal(x.Uri.ToString(), y.Uri.ToString()));
104
                    //But check to ensure that we DO have it's parent on record
105
                    //It it exist
106
                    if (lookups.TryGetValue(parentPath, out parent))
107
                    {
108
                        //Just continue
109
                        continue;
110
                    }
111
                    //If the item is directly below its parent container, there is no path to add
112
                    if (String.IsNullOrWhiteSpace(parentName))
113
                        continue;
114
                    //Otherwise we need to add it, because it is missing from the list
115
                    //Store each item using its current path
116
                    newNode = new DirectoryRecord { DisplayName = parentPath.Split('/').Last(), 
117
                        ObjectInfo = new ObjectInfo{Account=item.Account,Container=item.Container,Name=parentPath,Content_Type="application/directory"}};
100 118
                }
101 119
                else
102
                    nodes.Add(newNode);
120
                {
121
                    //Store each item using its current path
122
                    newNode = new DirectoryRecord {DisplayName = item.Name.Split('/').Last(), ObjectInfo = item};
123
                }
124
                AddNode(rootNodes, parentPath, path, lookups, newNode);
125
            }
126
            return rootNodes;
127
        }
128

  
129
        private static void AddNode(List<DirectoryRecord> rootNodes, string parentPath, string path, Dictionary<string, DirectoryRecord> lookups, DirectoryRecord newNode)
130
        {
131
            DirectoryRecord parent;
132
            lookups[path] = newNode;
103 133

  
134

  
135
            //Does a parent item exist? 
136
            if (lookups.TryGetValue(parentPath, out parent))
137
            {
138
                //If so, add the current item under its parent
139
                parent.Directories.Add(newNode);
140
                parent.Directories.Sort((x, y) => String.CompareOrdinal(x.Uri.ToString(), y.Uri.ToString()));
104 141
            }
105
            return nodes;
142
            else
143
                //Otherwise add it to the list of root nodes
144
                rootNodes.Add(newNode);
145
        }
146

  
147
        private static string GetParentPath(string path)
148
        {            
149
            var lastIndex = path.LastIndexOf("/", StringComparison.Ordinal);
150
            if (lastIndex < 0)
151
                return null;
152
            var parentPath = path.Substring(0, lastIndex);
153
            return parentPath;
154
        }
155

  
156
        static readonly Regex PascalCaseRegex = new Regex("[a-z][A-Z]", RegexOptions.Compiled);        
157
        public static string Name(this  Enum value)
158
        {
159
            var name = Enum.GetName(value.GetType(), value);            
160
            return PascalCaseRegex.Replace(name, m => m.Value[0] + " " + char.ToLower(m.Value[1]));            
106 161
        }
107 162
    }
108 163
}
b/trunk/Pithos.Core/Agents/StatusAgent.cs
363 363
            }
364 364
        }
365 365

  
366
        private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
366
        private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus, string conflictReason)
367 367
        {
368 368
            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
369 369
            {
......
376 376
                    using (
377 377
                        var command =
378 378
                            new SQLiteCommand(
379
                                "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE ",
379
                                "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus,ConflictReason= :conflictReason where FilePath = :path COLLATE NOCASE ",
380 380
                                connection))
381 381
                    {
382 382

  
383 383
                        command.Parameters.AddWithValue("path", absolutePath);
384 384
                        command.Parameters.AddWithValue("fileStatus", fileStatus);
385 385
                        command.Parameters.AddWithValue("overlayStatus", overlayStatus);
386
                        command.Parameters.AddWithValue("conflictReason", conflictReason);
386 387
                        
387 388
                        var affected = command.ExecuteNonQuery();
388 389
                        if (affected == 0)
......
390 391
                            var createdState=FileState.CreateFor(FileInfoExtensions.FromPath(absolutePath));
391 392
                            createdState.FileStatus = fileStatus;
392 393
                            createdState.OverlayStatus = overlayStatus;
394
                            createdState.ConflictReason = conflictReason;
393 395
                            createdState.Create();  
394 396
                        }
395 397
                        return affected;
......
582 584
            _persistenceAgent.Post(() =>FileState.RenameState(oldPath, newPath));
583 585
        }*/
584 586

  
585
        public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus, string localFileMissingFromServer)
587
        public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus, string conflictReason)
586 588
        {
587 589
            if (String.IsNullOrWhiteSpace(path))
588 590
                throw new ArgumentNullException("path");
......
593 595
            Debug.Assert(!path.Contains(FolderConstants.CacheFolder));
594 596
            Debug.Assert(!path.EndsWith(".ignore"));
595 597

  
596
            _persistenceAgent.Post(() => UpdateStatusDirect(path, fileStatus, overlayStatus));
598
            _persistenceAgent.Post(() => UpdateStatusDirect(path, fileStatus, overlayStatus, conflictReason));
597 599
        }
598 600

  
599 601
/*
b/trunk/Pithos.Network/CloudFilesClient.cs
1416 1416
                    client.PutWithRetry(fileUrl, 3, @"application/octet-stream");
1417 1417

  
1418 1418
                    var expectedCodes = new[] { HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created};
1419
                    return (expectedCodes.Contains(client.StatusCode));
1419
                    var result=(expectedCodes.Contains(client.StatusCode));
1420
                    DeleteObject(account, cloudFile.Container, fileUrl);
1421
                    return result;
1420 1422
                }
1421 1423
                catch
1422 1424
                {
1423 1425
                    return false;
1424 1426
                }
1425
                finally
1426
                {
1427
                    DeleteObject(account,cloudFile.Container,fileUrl);                    
1428
                }                
1429 1427
            }
1430 1428
        }
1431 1429
    }

Also available in: Unified diff