Revision d21f3c77

b/trunk/Pithos.Client.WPF/FileProperties/FilePropertiesView.xaml
102 102
                <TabItem Header="Permissions">
103 103
                    <StackPanel>
104 104
                        <StackPanel Orientation="Horizontal">
105
                            <TextBox x:Name="PermissionName" Width="200"/>
106
                            <CheckBox x:Name="PermissionIsReadOnly" Content="Is Read Only" />
107
                            <Button x:Name="AddPermission" Content="Add"/>
105
                            <TextBox x:Name="PermissionName" Width="200" Margin="5"/>
106
                            <RadioButton x:Name="PermissionRead" Content="Read" Margin="5"/>
107
                            <RadioButton x:Name="PermissionWrite" Content="Write" Margin="5"/>
108
                            <Button x:Name="AddPermission" Content="Add" Margin="5"/>
108 109
                        </StackPanel>
109 110
                        <TextBlock Margin="5" Visibility="{Binding Path=IsPublic,FallbackValue=Collapsed, Converter={StaticResource BoolToVisible}}">
110 111
                <Run Text="Public URL:" />
111 112
                <Run Text="{Binding PublicUrl,FallbackValue='http://someurl'}" />
112 113
                        </TextBlock>
113 114
                        <CheckBox x:Name="IsPublic" Content="Public" Margin="5"/>
114
                        <DataGrid ItemsSource="{Binding Permissions}" 
115
                        <DataGrid x:Name="Permissions" ItemsSource="{Binding Permissions}" 
115 116
                AutoGenerateColumns="False" CanUserAddRows="True">
116 117
                            <DataGrid.Columns>
117 118
                                <DataGridTemplateColumn >
b/trunk/Pithos.Client.WPF/FileProperties/FilePropertiesViewModel.cs
284 284
            }
285 285
        }
286 286

  
287
        private bool _permissionIsReadOnly;
288
        public bool PermissionIsReadOnly
287
        private Permission _currentPermission;
288
        public Permission CurrentPermission
289 289
        {
290
            get { return _permissionIsReadOnly; }
290
            get { return _currentPermission; }
291 291
            set
292 292
            {
293
                _permissionIsReadOnly = value;
294
                NotifyOfPropertyChange(()=>PermissionIsReadOnly);
293
                _currentPermission = value;
294
                NotifyOfPropertyChange(()=>CurrentPermission);
295
            }
296
        }
297

  
298

  
299
        private bool _permissionRead;
300
        public bool PermissionRead
301
        {
302
            get { return _permissionRead; }
303
            set
304
            {
305
                _permissionRead = value;
306
                NotifyOfPropertyChange(()=>PermissionRead);
307
                if (CurrentPermission != null)
308
                {
309
                    CurrentPermission.Read = value;
310
                }
311
            }
312
        }
313
        public bool PermissionWrite
314
        {
315
            get { return CurrentPermission.Write; }
316
            set
317
            {
318
                CurrentPermission.Write = value;
319
                NotifyOfPropertyChange(()=>PermissionWrite);
295 320
            }
296 321
        }
297 322

  
......
302 327

  
303 328
        public void AddPermission()
304 329
        {
305
            Permissions.Add(new Permission{Read=PermissionIsReadOnly,UserName=PermissionName,Write=!PermissionIsReadOnly});   
330
            Permissions.Add(new Permission{Read=PermissionRead,UserName=PermissionName,Write=!PermissionRead});   
306 331
        }
307 332

  
308 333

  
b/trunk/Pithos.Client.WPF/Pithos.Client.WPF.csproj
249 249
      <HintPath>..\Libraries\Caliburn.Micro.dll</HintPath>
250 250
    </Reference>
251 251
    <Reference Include="Castle.ActiveRecord, Version=3.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL" />
252
    <Reference Include="Castle.Components.Validator">
253
      <HintPath>..\Libraries\Castle.Components.Validator.dll</HintPath>
254
    </Reference>
255
    <Reference Include="Castle.Core">
256
      <HintPath>..\Libraries\Castle.Core.dll</HintPath>
257
    </Reference>
252 258
    <Reference Include="log4net">
253 259
      <HintPath>..\Libraries\log4net.dll</HintPath>
254 260
    </Reference>
261
    <Reference Include="NHibernate.ByteCode.Castle">
262
      <HintPath>..\Libraries\NHibernate.ByteCode.Castle.dll</HintPath>
263
    </Reference>
255 264
    <Reference Include="System" />
256 265
    <Reference Include="System.ComponentModel.Composition" />
257 266
    <Reference Include="System.Configuration.Install" />
b/trunk/Pithos.Core/Agents/Downloader.cs
1
using System;
2
using System.Collections.Generic;
3
using System.ComponentModel.Composition;
4
using System.Diagnostics.Contracts;
5
using System.IO;
6
using System.Linq;
7
using System.Reflection;
8
using System.Threading;
9
using System.Threading.Tasks;
10
using Pithos.Interfaces;
11
using Pithos.Network;
12
using log4net;
13

  
14
namespace Pithos.Core.Agents
15
{
16
    [Export(typeof(Downloader))]
17
    public class Downloader
18
    {
19
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
20

  
21
        [Import]
22
        private IStatusKeeper StatusKeeper { get; set; }
23

  
24
        
25
        public IStatusNotification StatusNotification { get; set; }
26

  
27
/*
28
        private CancellationTokenSource _cts=new CancellationTokenSource();
29

  
30
        public void SignalStop()
31
        {
32
            _cts.Cancel();
33
        }
34
*/
35

  
36

  
37
        //Download a file.
38
        public async Task DownloadCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile, string filePath,CancellationToken cancellationToken)
39
        {
40
            if (accountInfo == null)
41
                throw new ArgumentNullException("accountInfo");
42
            if (cloudFile == null)
43
                throw new ArgumentNullException("cloudFile");
44
            if (String.IsNullOrWhiteSpace(cloudFile.Account))
45
                throw new ArgumentNullException("cloudFile");
46
            if (String.IsNullOrWhiteSpace(cloudFile.Container))
47
                throw new ArgumentNullException("cloudFile");
48
            if (String.IsNullOrWhiteSpace(filePath))
49
                throw new ArgumentNullException("filePath");
50
            if (!Path.IsPathRooted(filePath))
51
                throw new ArgumentException("The filePath must be rooted", "filePath");
52
            Contract.EndContractBlock();
53
                using (ThreadContext.Stacks["Operation"].Push("DownloadCloudFile"))
54
                {
1
using System;

2
using System.Collections.Generic;

3
using System.ComponentModel.Composition;

4
using System.Diagnostics.Contracts;

5
using System.IO;

6
using System.Linq;

7
using System.Reflection;

8
using System.Threading;

9
using System.Threading.Tasks;

10
using Pithos.Interfaces;

11
using Pithos.Network;

12
using log4net;

13

  
14
namespace Pithos.Core.Agents

15
{

16
    [Export(typeof(Downloader))]

17
    public class Downloader

18
    {

19
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

20

  
21
        [Import]

22
        private IStatusKeeper StatusKeeper { get; set; }

23

  
24
        
25
        public IStatusNotification StatusNotification { get; set; }

26

  
27
/*

28
        private CancellationTokenSource _cts=new CancellationTokenSource();

29

  
30
        public void SignalStop()

31
        {

32
            _cts.Cancel();

33
        }

34
*/

35

  
36

  
37
        //Download a file.

38
        public async Task DownloadCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile, string filePath,CancellationToken cancellationToken)

39
        {

40
            if (accountInfo == null)

41
                throw new ArgumentNullException("accountInfo");

42
            if (cloudFile == null)

43
                throw new ArgumentNullException("cloudFile");

44
            if (String.IsNullOrWhiteSpace(cloudFile.Account))

45
                throw new ArgumentNullException("cloudFile");

46
            if (String.IsNullOrWhiteSpace(cloudFile.Container))

47
                throw new ArgumentNullException("cloudFile");

48
            if (String.IsNullOrWhiteSpace(filePath))

49
                throw new ArgumentNullException("filePath");

50
            if (!Path.IsPathRooted(filePath))

51
                throw new ArgumentException("The filePath must be rooted", "filePath");

52
            Contract.EndContractBlock();

53
                using (ThreadContext.Stacks["Operation"].Push("DownloadCloudFile"))

54
                {

55 55
                   // var cancellationToken=_cts.Token;//  .ThrowIfCancellationRequested();
56 56

  
57 57
                    if (await WaitOrAbort(cloudFile, cancellationToken))
58
                        return;
59

  
60

  
61
                    var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);
62
                    var relativeUrl = new Uri(cloudFile.Name, UriKind.Relative);
63

  
64
                    var url = relativeUrl.ToString();
65
                    if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))
66
                        return;
67

  
68
                    if (!Selectives.IsSelected(cloudFile))
69
                        return;
70

  
71

  
72
                    //Are we already downloading or uploading the file? 
73
                    using (var gate = NetworkGate.Acquire(localPath, NetworkOperation.Downloading))
74
                    {
75
                        if (gate.Failed)
76
                            return;
77

  
78
                        var client = new CloudFilesClient(accountInfo);
79
                        var account = cloudFile.Account;
80
                        var container = cloudFile.Container;
81

  
82
                        if (cloudFile.IsDirectory)
83
                        {
84
                            if (!Directory.Exists(localPath))
85
                                try
86
                                {
87
                                    Directory.CreateDirectory(localPath);
88
                                    if (Log.IsDebugEnabled)
89
                                        Log.DebugFormat("Created Directory [{0}]", localPath);
90
                                }
91
                                catch (IOException)
92
                                {
93
                                    var localInfo = new FileInfo(localPath);
94
                                    if (localInfo.Exists && localInfo.Length == 0)
95
                                    {
96
                                        Log.WarnFormat("Malformed directory object detected for [{0}]", localPath);
97
                                        localInfo.Delete();
98
                                        Directory.CreateDirectory(localPath);
99
                                        if (Log.IsDebugEnabled)
100
                                            Log.DebugFormat("Created Directory [{0}]", localPath);
101
                                    }
102
                                }
103
                        }
104
                        else
105
                        {
106
                            var isChanged = IsObjectChanged(cloudFile, localPath);
107
                            if (isChanged)
108
                            {
109
                                //Retrieve the hashmap from the server
110
                                var serverHash = await client.GetHashMap(account, container, url);
111
                                //If it's a small file
112
                                if (serverHash.Hashes.Count == 1)
113
                                    //Download it in one go
114
                                    await
115
                                        DownloadEntireFileAsync(accountInfo, client, cloudFile, relativeUrl, localPath,cancellationToken);
116
                                    //Otherwise download it block by block
117
                                else
118
                                    await
119
                                        DownloadWithBlocks(accountInfo, client, cloudFile, relativeUrl, localPath,
120
                                                           serverHash,cancellationToken);
121

  
122
                                if (!cloudFile.IsWritable(accountInfo.UserName))
123
                                {
124
                                    var attributes = File.GetAttributes(localPath);
125
                                    File.SetAttributes(localPath, attributes | FileAttributes.ReadOnly);
126
                                }
127
                            }
128
                        }
129

  
130
                        //Now we can store the object's metadata without worrying about ghost status entries
131
                        StatusKeeper.StoreInfo(localPath, cloudFile);
132

  
133
                    }
134
                }
135
           
136
        }
137

  
138
        //Download a file asynchronously using blocks
139
        public async Task DownloadWithBlocks(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string filePath, TreeHash serverHash, CancellationToken cancellationToken)
140
        {
141
            if (client == null)
142
                throw new ArgumentNullException("client");
143
            if (cloudFile == null)
144
                throw new ArgumentNullException("cloudFile");
145
            if (relativeUrl == null)
146
                throw new ArgumentNullException("relativeUrl");
147
            if (String.IsNullOrWhiteSpace(filePath))
148
                throw new ArgumentNullException("filePath");
149
            if (!Path.IsPathRooted(filePath))
150
                throw new ArgumentException("The filePath must be rooted", "filePath");
151
            if (serverHash == null)
152
                throw new ArgumentNullException("serverHash");
153
            if (cloudFile.IsDirectory)
154
                throw new ArgumentException("cloudFile is a directory, not a file", "cloudFile");
58
                        return;

59

  
60

  
61
                    var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);

62
                    var relativeUrl = new Uri(cloudFile.Name, UriKind.Relative);

63

  
64
                    var url = relativeUrl.ToString();

65
                    if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))

66
                        return;

67

  
68
                    if (!Selectives.IsSelected(cloudFile))

69
                        return;

70

  
71

  
72
                    //Are we already downloading or uploading the file? 

73
                    using (var gate = NetworkGate.Acquire(localPath, NetworkOperation.Downloading))

74
                    {

75
                        if (gate.Failed)

76
                            return;

77

  
78
                        var client = new CloudFilesClient(accountInfo);

79
                        var account = cloudFile.Account;

80
                        var container = cloudFile.Container;

81

  
82
                        if (cloudFile.IsDirectory)

83
                        {

84
                            if (!Directory.Exists(localPath))

85
                                try

86
                                {

87
                                    Directory.CreateDirectory(localPath);

88
                                    if (Log.IsDebugEnabled)

89
                                        Log.DebugFormat("Created Directory [{0}]", localPath);

90
                                }

91
                                catch (IOException)

92
                                {

93
                                    var localInfo = new FileInfo(localPath);

94
                                    if (localInfo.Exists && localInfo.Length == 0)

95
                                    {

96
                                        Log.WarnFormat("Malformed directory object detected for [{0}]", localPath);

97
                                        localInfo.Delete();

98
                                        Directory.CreateDirectory(localPath);

99
                                        if (Log.IsDebugEnabled)

100
                                            Log.DebugFormat("Created Directory [{0}]", localPath);

101
                                    }

102
                                }

103
                        }

104
                        else

105
                        {

106
                            var isChanged = IsObjectChanged(cloudFile, localPath);

107
                            if (isChanged)

108
                            {

109
                                //Retrieve the hashmap from the server

110
                                var serverHash = await client.GetHashMap(account, container, url);

111
                                //If it's a small file

112
                                if (serverHash.Hashes.Count == 1)

113
                                    //Download it in one go

114
                                    await

115
                                        DownloadEntireFileAsync(accountInfo, client, cloudFile, relativeUrl, localPath,cancellationToken);

116
                                    //Otherwise download it block by block

117
                                else

118
                                    await

119
                                        DownloadWithBlocks(accountInfo, client, cloudFile, relativeUrl, localPath,

120
                                                           serverHash,cancellationToken);

121

  
122
                                if (!cloudFile.IsWritable(accountInfo.UserName))

123
                                {

124
                                    var attributes = File.GetAttributes(localPath);

125
                                    File.SetAttributes(localPath, attributes | FileAttributes.ReadOnly);

126
                                }

127
                            }

128
                        }

129

  
130
                        //Now we can store the object's metadata without worrying about ghost status entries

131
                        StatusKeeper.StoreInfo(localPath, cloudFile);

132

  
133
                    }

134
                }

135
           
136
        }

137

  
138
        //Download a file asynchronously using blocks

139
        public async Task DownloadWithBlocks(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string filePath, TreeHash serverHash, CancellationToken cancellationToken)

140
        {

141
            if (client == null)

142
                throw new ArgumentNullException("client");

143
            if (cloudFile == null)

144
                throw new ArgumentNullException("cloudFile");

145
            if (relativeUrl == null)

146
                throw new ArgumentNullException("relativeUrl");

147
            if (String.IsNullOrWhiteSpace(filePath))

148
                throw new ArgumentNullException("filePath");

149
            if (!Path.IsPathRooted(filePath))

150
                throw new ArgumentException("The filePath must be rooted", "filePath");

151
            if (serverHash == null)

152
                throw new ArgumentNullException("serverHash");

153
            if (cloudFile.IsDirectory)

154
                throw new ArgumentException("cloudFile is a directory, not a file", "cloudFile");

155 155
            Contract.EndContractBlock();
156 156

  
157 157
            if (await WaitOrAbort(cloudFile, cancellationToken))
158
                return;
159

  
160
            var fileAgent = GetFileAgent(accountInfo);
161
            var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);
162

  
163
            //Calculate the relative file path for the new file
164
            var relativePath = relativeUrl.RelativeUriToFilePath();
165
            var blockUpdater = new BlockUpdater(fileAgent.CachePath, localPath, relativePath, serverHash);
166

  
167
            StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing, String.Format("Calculating hashmap for {0} before download", Path.GetFileName(localPath)));
168
            //Calculate the file's treehash
169

  
170
            //TODO: Should pass cancellation token here
171
            var treeHash = await Signature.CalculateTreeHashAsync(localPath, (int)serverHash.BlockSize, serverHash.BlockHash, 2);
172

  
173
            //And compare it with the server's hash
174
            var upHashes = serverHash.GetHashesAsStrings();
175
            var localHashes = treeHash.HashDictionary;
176
            ReportDownloadProgress(Path.GetFileName(localPath), 0, upHashes.Length, cloudFile.Bytes);
177
            for (var i = 0; i < upHashes.Length; i++)
158
                return;

159

  
160
            var fileAgent = GetFileAgent(accountInfo);

161
            var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);

162

  
163
            //Calculate the relative file path for the new file

164
            var relativePath = relativeUrl.RelativeUriToFilePath();

165
            var blockUpdater = new BlockUpdater(fileAgent.CachePath, localPath, relativePath, serverHash);

166

  
167
            StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing, String.Format("Calculating hashmap for {0} before download", Path.GetFileName(localPath)));

168
            //Calculate the file's treehash

169

  
170
            //TODO: Should pass cancellation token here

171
            var treeHash = await Signature.CalculateTreeHashAsync(localPath, (int)serverHash.BlockSize, serverHash.BlockHash, 2);

172

  
173
            //And compare it with the server's hash

174
            var upHashes = serverHash.GetHashesAsStrings();

175
            var localHashes = treeHash.HashDictionary;

176
            ReportDownloadProgress(Path.GetFileName(localPath), 0, upHashes.Length, cloudFile.Bytes);

177
            for (var i = 0; i < upHashes.Length; i++)

178 178
            {
179 179
                if (await WaitOrAbort(cloudFile, cancellationToken))
180
                    return;
181

  
182
                //For every non-matching hash
183
                var upHash = upHashes[i];
184
                if (!localHashes.ContainsKey(upHash))
185
                {
186
                    StatusNotification.Notify(new CloudNotification { Data = cloudFile });
187

  
188
                    if (blockUpdater.UseOrphan(i, upHash))
189
                    {
190
                        Log.InfoFormat("[BLOCK GET] ORPHAN FOUND for {0} of {1} for {2}", i, upHashes.Length, localPath);
191
                        continue;
192
                    }
193
                    Log.InfoFormat("[BLOCK GET] START {0} of {1} for {2}", i, upHashes.Length, localPath);
194
                    var start = i * serverHash.BlockSize;
195
                    //To download the last block just pass a null for the end of the range
196
                    long? end = null;
197
                    if (i < upHashes.Length - 1)
198
                        end = ((i + 1) * serverHash.BlockSize);
199

  
200
                    //TODO: Pass token here
201
                    //Download the missing block
202
                    var block = await client.GetBlock(cloudFile.Account, cloudFile.Container, relativeUrl, start, end,cancellationToken);
203

  
204
                    //and store it
205
                    blockUpdater.StoreBlock(i, block);
206

  
207

  
208
                    Log.InfoFormat("[BLOCK GET] FINISH {0} of {1} for {2}", i, upHashes.Length, localPath);
209
                }
210
                ReportDownloadProgress(Path.GetFileName(localPath), i, upHashes.Length, cloudFile.Bytes);
211
            }
212

  
213
            //Want to avoid notifications if no changes were made
214
            var hasChanges = blockUpdater.HasBlocks;
215
            blockUpdater.Commit();
216

  
217
            if (hasChanges)
218
                //Notify listeners that a local file has changed
219
                StatusNotification.NotifyChangedFile(localPath);
220

  
221
            Log.InfoFormat("[BLOCK GET] COMPLETE {0}", localPath);
222
        }
223

  
224
        //Download a small file with a single GET operation
225
        private async Task DownloadEntireFileAsync(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string filePath, CancellationToken cancellationToken)
226
        {
227
            if (client == null)
228
                throw new ArgumentNullException("client");
229
            if (cloudFile == null)
230
                throw new ArgumentNullException("cloudFile");
231
            if (relativeUrl == null)
232
                throw new ArgumentNullException("relativeUrl");
233
            if (String.IsNullOrWhiteSpace(filePath))
234
                throw new ArgumentNullException("filePath");
235
            if (!Path.IsPathRooted(filePath))
236
                throw new ArgumentException("The localPath must be rooted", "filePath");
237
            if (cloudFile.IsDirectory)
238
                throw new ArgumentException("cloudFile is a directory, not a file", "cloudFile");
180
                    return;

181

  
182
                //For every non-matching hash

183
                var upHash = upHashes[i];

184
                if (!localHashes.ContainsKey(upHash))

185
                {

186
                    StatusNotification.Notify(new CloudNotification { Data = cloudFile });

187

  
188
                    if (blockUpdater.UseOrphan(i, upHash))

189
                    {

190
                        Log.InfoFormat("[BLOCK GET] ORPHAN FOUND for {0} of {1} for {2}", i, upHashes.Length, localPath);

191
                        continue;

192
                    }

193
                    Log.InfoFormat("[BLOCK GET] START {0} of {1} for {2}", i, upHashes.Length, localPath);

194
                    var start = i * serverHash.BlockSize;

195
                    //To download the last block just pass a null for the end of the range

196
                    long? end = null;

197
                    if (i < upHashes.Length - 1)

198
                        end = ((i + 1) * serverHash.BlockSize);

199

  
200
                    //TODO: Pass token here

201
                    //Download the missing block

202
                    var block = await client.GetBlock(cloudFile.Account, cloudFile.Container, relativeUrl, start, end,cancellationToken);

203

  
204
                    //and store it

205
                    blockUpdater.StoreBlock(i, block);

206

  
207

  
208
                    Log.InfoFormat("[BLOCK GET] FINISH {0} of {1} for {2}", i, upHashes.Length, localPath);

209
                }

210
                ReportDownloadProgress(Path.GetFileName(localPath), i, upHashes.Length, cloudFile.Bytes);

211
            }

212

  
213
            //Want to avoid notifications if no changes were made

214
            var hasChanges = blockUpdater.HasBlocks;

215
            blockUpdater.Commit();

216

  
217
            if (hasChanges)

218
                //Notify listeners that a local file has changed

219
                StatusNotification.NotifyChangedFile(localPath);

220

  
221
            Log.InfoFormat("[BLOCK GET] COMPLETE {0}", localPath);

222
        }

223

  
224
        //Download a small file with a single GET operation

225
        private async Task DownloadEntireFileAsync(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string filePath, CancellationToken cancellationToken)

226
        {

227
            if (client == null)

228
                throw new ArgumentNullException("client");

229
            if (cloudFile == null)

230
                throw new ArgumentNullException("cloudFile");

231
            if (relativeUrl == null)

232
                throw new ArgumentNullException("relativeUrl");

233
            if (String.IsNullOrWhiteSpace(filePath))

234
                throw new ArgumentNullException("filePath");

235
            if (!Path.IsPathRooted(filePath))

236
                throw new ArgumentException("The localPath must be rooted", "filePath");

237
            if (cloudFile.IsDirectory)

238
                throw new ArgumentException("cloudFile is a directory, not a file", "cloudFile");

239 239
            Contract.EndContractBlock();
240 240

  
241 241
            if (await WaitOrAbort(cloudFile, cancellationToken))
242
                return;
243

  
244
            var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);
245
            StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing, String.Format("Downloading {0}", Path.GetFileName(localPath)));
246
            StatusNotification.Notify(new CloudNotification { Data = cloudFile });
247

  
248
            var fileAgent = GetFileAgent(accountInfo);
249
            //Calculate the relative file path for the new file
250
            var relativePath = relativeUrl.RelativeUriToFilePath();
251
            //The file will be stored in a temporary location while downloading with an extension .download
252
            var tempPath = Path.Combine(fileAgent.CachePath, relativePath + ".download");
253
            //Make sure the target folder exists. DownloadFileTask will not create the folder
254
            var tempFolder = Path.GetDirectoryName(tempPath);
255
            if (!Directory.Exists(tempFolder))
256
                Directory.CreateDirectory(tempFolder);
257

  
258
            //TODO: Should pass the token here
259

  
260
            //Download the object to the temporary location
261
            await client.GetObject(cloudFile.Account, cloudFile.Container, relativeUrl.ToString(), tempPath,cancellationToken);
262

  
263
            //Create the local folder if it doesn't exist (necessary for shared objects)
264
            var localFolder = Path.GetDirectoryName(localPath);
265
            if (!Directory.Exists(localFolder))
266
                try
267
                {
268
                    Directory.CreateDirectory(localFolder);
269
                }
270
                catch (IOException)
271
                {
272
                    //A file may already exist that has the same name as the new folder.
273
                    //This may be an artifact of the way Pithos handles directories
274
                    var fileInfo = new FileInfo(localFolder);
275
                    if (fileInfo.Exists && fileInfo.Length == 0)
276
                    {
277
                        Log.WarnFormat("Malformed directory object detected for [{0}]", localFolder);
278
                        fileInfo.Delete();
279
                        Directory.CreateDirectory(localFolder);
280
                    }
281
                    else
282
                        throw;
283
                }
284
            //And move it to its actual location once downloading is finished
285
            if (File.Exists(localPath))
286
                File.Replace(tempPath, localPath, null, true);
287
            else
288
                File.Move(tempPath, localPath);
289
            //Notify listeners that a local file has changed
290
            StatusNotification.NotifyChangedFile(localPath);
291

  
292

  
293
        }
294

  
295

  
296
        private void ReportDownloadProgress(string fileName, int block, int totalBlocks, long fileSize)
297
        {
298
            StatusNotification.Notify(totalBlocks == 0
299
                                          ? new ProgressNotification(fileName, "Downloading", 1, 1, fileSize)
300
                                          : new ProgressNotification(fileName, "Downloading", block, totalBlocks, fileSize));
301
        }
302

  
303
        private bool IsObjectChanged(ObjectInfo cloudFile, string localPath)
304
        {
305
            //If the target is a directory, there are no changes to download
306
            if (Directory.Exists(localPath))
307
                return false;
308
            //If the file doesn't exist, we have a chagne
309
            if (!File.Exists(localPath))
310
                return true;
311
            //If there is no stored state, we have a change
312
            var localState = StatusKeeper.GetStateByFilePath(localPath);
313
            if (localState == null)
314
                return true;
315

  
316
            var info = new FileInfo(localPath);
317
            var shortHash = info.ComputeShortHash();
318
            //If the file is different from the stored state, we have a change
319
            if (localState.ShortHash != shortHash)
320
                return true;
321
            //If the top hashes differ, we have a change
322
            return (localState.Checksum != cloudFile.Hash);
323
        }
324

  
325
        private static FileAgent GetFileAgent(AccountInfo accountInfo)
326
        {
327
            return AgentLocator<FileAgent>.Get(accountInfo.AccountPath);
242
                return;

243

  
244
            var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);

245
            StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing, String.Format("Downloading {0}", Path.GetFileName(localPath)));

246
            StatusNotification.Notify(new CloudNotification { Data = cloudFile });

247

  
248
            var fileAgent = GetFileAgent(accountInfo);

249
            //Calculate the relative file path for the new file

250
            var relativePath = relativeUrl.RelativeUriToFilePath();

251
            //The file will be stored in a temporary location while downloading with an extension .download

252
            var tempPath = Path.Combine(fileAgent.CachePath, relativePath + ".download");

253
            //Make sure the target folder exists. DownloadFileTask will not create the folder

254
            var tempFolder = Path.GetDirectoryName(tempPath);

255
            if (!Directory.Exists(tempFolder))

256
                Directory.CreateDirectory(tempFolder);

257

  
258
            //TODO: Should pass the token here

259

  
260
            //Download the object to the temporary location

261
            await client.GetObject(cloudFile.Account, cloudFile.Container, relativeUrl.ToString(), tempPath,cancellationToken);

262

  
263
            //Create the local folder if it doesn't exist (necessary for shared objects)

264
            var localFolder = Path.GetDirectoryName(localPath);

265
            if (!Directory.Exists(localFolder))

266
                try

267
                {

268
                    Directory.CreateDirectory(localFolder);

269
                }

270
                catch (IOException)

271
                {

272
                    //A file may already exist that has the same name as the new folder.

273
                    //This may be an artifact of the way Pithos handles directories

274
                    var fileInfo = new FileInfo(localFolder);

275
                    if (fileInfo.Exists && fileInfo.Length == 0)

276
                    {

277
                        Log.WarnFormat("Malformed directory object detected for [{0}]", localFolder);

278
                        fileInfo.Delete();

279
                        Directory.CreateDirectory(localFolder);

280
                    }

281
                    else

282
                        throw;

283
                }

284
            //And move it to its actual location once downloading is finished

285
            if (File.Exists(localPath))

286
                File.Replace(tempPath, localPath, null, true);

287
            else

288
                File.Move(tempPath, localPath);

289
            //Notify listeners that a local file has changed

290
            StatusNotification.NotifyChangedFile(localPath);

291

  
292

  
293
        }

294

  
295

  
296
        private void ReportDownloadProgress(string fileName, int block, int totalBlocks, long fileSize)

297
        {

298
            StatusNotification.Notify(totalBlocks == 0

299
                                          ? new ProgressNotification(fileName, "Downloading", 1, 1, fileSize)

300
                                          : new ProgressNotification(fileName, "Downloading", block, totalBlocks, fileSize));

301
        }

302

  
303
        private bool IsObjectChanged(ObjectInfo cloudFile, string localPath)

304
        {

305
            //If the target is a directory, there are no changes to download

306
            if (Directory.Exists(localPath))

307
                return false;

308
            //If the file doesn't exist, we have a chagne

309
            if (!File.Exists(localPath))

310
                return true;

311
            //If there is no stored state, we have a change

312
            var localState = StatusKeeper.GetStateByFilePath(localPath);

313
            if (localState == null)

314
                return true;

315

  
316
            var info = new FileInfo(localPath);

317
            var shortHash = info.ComputeShortHash();

318
            //If the file is different from the stored state, we have a change

319
            if (localState.ShortHash != shortHash)

320
                return true;

321
            //If the top hashes differ, we have a change

322
            return (localState.Checksum != cloudFile.Hash);

323
        }

324

  
325
        private static FileAgent GetFileAgent(AccountInfo accountInfo)

326
        {

327
            return AgentLocator<FileAgent>.Get(accountInfo.AccountPath);

328 328
        }
329 329

  
330 330
        private async Task<bool> WaitOrAbort(ObjectInfo cloudFile, CancellationToken token)
......
335 335
            if (shouldAbort)
336 336
                Log.InfoFormat("Aborting [{0}]", cloudFile.Uri);
337 337
            return shouldAbort;
338
        }
339

  
340
        [Import]
341
        public Selectives Selectives { get; set; }
342

  
343
        public AsyncManualResetEvent UnpauseEvent { get; set; }
344
    }
345
}
338
        }
339

  
340
        [Import]
341
        public Selectives Selectives { get; set; }
342

  
343
        public AsyncManualResetEvent UnpauseEvent { get; set; }
344
    }
345
}
b/trunk/Pithos.Core/Agents/NetworkAgent.cs
409 409
                    return;
410 410
                }
411 411

  
412
                //If the local and remote files have 0 length their hashes will not match
413
                if (!cloudFile.IsDirectory && cloudFile.Bytes==0 && action.LocalFile is FileInfo && (action.LocalFile as FileInfo).Length==0 )
414
                {
415
                    Log.InfoFormat("Skipping {0}, files are empty", downloadPath);
416
                    return;
417
                }
418

  
412 419
                //The hashes DON'T match. We need to sync
413 420

  
414 421
                // If the previous tophash matches the local tophash, the file was only changed on the server. 
b/trunk/Pithos.Core/Agents/PollAgent.cs
660 660
            var client = new CloudFilesClient(accountInfo);
661 661
            foreach (var folderUri in added)
662 662
            {
663
                string account;
664
                string container;
665
                var segmentsCount = folderUri.Segments.Length;
666
                if (segmentsCount < 3)
667
                    continue;
668
                if (segmentsCount==3)
669
                {
670
                    account = folderUri.Segments[1].TrimEnd('/');
671
                    container = folderUri.Segments[2].TrimEnd('/');                    
672
                }
673
                else
674
                {
675
                    account = folderUri.Segments[2].TrimEnd('/');
676
                    container = folderUri.Segments[3].TrimEnd('/');                    
677
                }
678
                IList<ObjectInfo> items;
679
                if(segmentsCount>3)
663
                try
680 664
                {
681
                    var folder =String.Join("", folderUri.Segments.Splice(4));
682
                    items = client.ListObjects(account, container, folder);
665

  
666
                    string account;
667
                    string container;
668
                    var segmentsCount = folderUri.Segments.Length;
669
                    if (segmentsCount < 3)
670
                        continue;
671
                    if (segmentsCount == 3)
672
                    {
673
                        account = folderUri.Segments[1].TrimEnd('/');
674
                        container = folderUri.Segments[2].TrimEnd('/');
675
                    }
676
                    else
677
                    {
678
                        account = folderUri.Segments[2].TrimEnd('/');
679
                        container = folderUri.Segments[3].TrimEnd('/');
680
                    }
681
                    IList<ObjectInfo> items;
682
                    if (segmentsCount > 3)
683
                    {
684
                        var folder = String.Join("", folderUri.Segments.Splice(4));
685
                        items = client.ListObjects(account, container, folder);
686
                    }
687
                    else
688
                    {
689
                        items = client.ListObjects(account, container);
690
                    }
691
                    var actions = CreatesToActions(accountInfo, items);
692
                    foreach (var action in actions)
693
                    {
694
                        NetworkAgent.Post(action);
695
                    }
683 696
                }
684
                else
697
                catch (Exception exc)
685 698
                {
686
                    items = client.ListObjects(account, container);
699
                    Log.WarnFormat("Listing of new selective path [{0}] failed with \r\n{1}", folderUri, exc);
687 700
                }
688
                var actions=CreatesToActions(accountInfo, items);
689
                foreach (var action in actions)
690
                {
691
                    NetworkAgent.Post(action);    
692
                }                
693 701
            }
694 702

  
695 703
            //Need to get a listing of each of the URLs, then post them to the NetworkAgent

Also available in: Unified diff