Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / BlockUpdater.cs @ 174bbb6e

History | View | Annotate | Download (11.6 kB)

1
#region
2
/* -----------------------------------------------------------------------
3
 * <copyright file="BlockUpdater.cs" company="GRNet">
4
 * 
5
 * Copyright 2011-2012 GRNET S.A. All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or
8
 * without modification, are permitted provided that the following
9
 * conditions are met:
10
 *
11
 *   1. Redistributions of source code must retain the above
12
 *      copyright notice, this list of conditions and the following
13
 *      disclaimer.
14
 *
15
 *   2. Redistributions in binary form must reproduce the above
16
 *      copyright notice, this list of conditions and the following
17
 *      disclaimer in the documentation and/or other materials
18
 *      provided with the distribution.
19
 *
20
 *
21
 * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
22
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
25
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
 * POSSIBILITY OF SUCH DAMAGE.
33
 *
34
 * The views and conclusions contained in the software and
35
 * documentation are those of the authors and should not be
36
 * interpreted as representing official policies, either expressed
37
 * or implied, of GRNET S.A.
38
 * </copyright>
39
 * -----------------------------------------------------------------------
40
 */
41
#endregion
42
using System;
43
using System.Collections.Concurrent;
44
using System.Collections.Generic;
45
using System.Diagnostics.Contracts;
46
using System.IO;
47
using System.Linq;
48
using System.Security.Cryptography;
49
using System.Text;
50
using System.Threading.Tasks;
51
using Pithos.Network;
52

    
53
namespace Pithos.Core.Agents
54
{
55
    class BlockUpdater
56
    {
57
        public string FilePath { get; private set; }
58
        public string RelativePath { get; private set; }
59

    
60
        public string CachePath { get; private set; }
61

    
62
        public TreeHash ServerHash { get; private set; }
63

    
64
        public string TempPath { get; private set; }
65

    
66
        public bool HasBlocks
67
        {
68
            get { return _blocks.Count>0; }            
69
        }
70

    
71
        readonly ConcurrentDictionary<int, string> _blocks = new ConcurrentDictionary<int, string>();
72
        readonly ConcurrentDictionary<string, string> _orphanBlocks = new ConcurrentDictionary<string, string>();
73

    
74
        [ContractInvariantMethod]
75
        private void Invariants()
76
        {
77
            Contract.Invariant(Path.IsPathRooted(CachePath));
78
            Contract.Invariant(Path.IsPathRooted(FilePath));
79
            Contract.Invariant(Path.IsPathRooted(TempPath));
80
            Contract.Invariant(!Path.IsPathRooted(RelativePath));
81
            Contract.Invariant(_blocks!=null);
82
            Contract.Invariant(_orphanBlocks!=null);
83
            Contract.Invariant(ServerHash!=null);
84
        }
85

    
86
        public BlockUpdater(string cachePath, string filePath, string relativePath,TreeHash serverHash)
87
        {   
88
            if (String.IsNullOrWhiteSpace(cachePath))
89
                throw new ArgumentNullException("cachePath");
90
            if (!Path.IsPathRooted(cachePath))
91
                throw new ArgumentException("The cachePath must be rooted", "cachePath");
92
            
93
            if (string.IsNullOrWhiteSpace(filePath))
94
                throw new ArgumentNullException("filePath");
95
            if (!Path.IsPathRooted(filePath))
96
                throw new ArgumentException("The filePath must be rooted", "filePath");
97
            
98
            if (string.IsNullOrWhiteSpace(relativePath))
99
                throw new ArgumentNullException("relativePath");
100
            if (Path.IsPathRooted(relativePath))
101
                throw new ArgumentException("The relativePath must NOT be rooted", "relativePath");
102

    
103
            if (serverHash == null)
104
                throw new ArgumentNullException("serverHash");
105
            Contract.EndContractBlock();
106

    
107
            CachePath=cachePath;
108
            FilePath = filePath;
109
            RelativePath=relativePath;
110
            ServerHash = serverHash;
111
            //The file will be stored in a temporary location while downloading with an extension .download
112
            TempPath = Path.Combine(CachePath, RelativePath + ".download");
113
            
114
            //Need to calculate the directory path because RelativePath may include folders
115
            var directoryPath = Path.GetDirectoryName(TempPath);            
116
            //directoryPath CAN be null if TempPath is a root path
117
            if (String.IsNullOrWhiteSpace(directoryPath))
118
                throw new ArgumentException("TempPath");
119
            //CachePath was absolute so directoryPath is absolute too
120
            Contract.Assume(Path.IsPathRooted(directoryPath));
121
            
122
            if (!Directory.Exists(directoryPath))
123
                Directory.CreateDirectory(directoryPath);
124

    
125
            LoadOrphans(directoryPath);
126
        }
127

    
128
        private void LoadOrphans(string directoryPath)
129
        {
130
            if (string.IsNullOrWhiteSpace(directoryPath))
131
                throw new ArgumentNullException("directoryPath");
132
            if (!Path.IsPathRooted(directoryPath))
133
                throw new ArgumentException("The directoryPath must be rooted", "directoryPath");
134
            if (ServerHash==null)
135
                throw new InvalidOperationException("ServerHash wasn't initialized");
136
            Contract.EndContractBlock();
137

    
138
            var fileNamename = Path.GetFileName(FilePath);
139
            var orphans = Directory.GetFiles(directoryPath, fileNamename + ".*");
140
            foreach (var orphan in orphans)
141
            {
142
                using (HashAlgorithm hasher = HashAlgorithm.Create(ServerHash.BlockHash))
143
                {
144
                    var buffer=File.ReadAllBytes(orphan);
145
                    //The server truncates nulls before calculating hashes, have to do the same
146
                    //Find the last non-null byte, starting from the end
147
                    var lastByteIndex = Array.FindLastIndex(buffer, buffer.Length-1, aByte => aByte != 0);
148
                    //lastByteIndex may be -1 if the file was empty. We don't want to use that block file
149
                    if (lastByteIndex >= 0)
150
                    {
151
                        var binHash = hasher.ComputeHash(buffer, 0, lastByteIndex);
152
                        var hash = binHash.ToHashString();
153
                        _orphanBlocks[hash] = orphan;
154
                    }
155
                }
156
            }
157
        }
158

    
159

    
160
        public void Commit()
161
        {
162
            if (String.IsNullOrWhiteSpace(FilePath))
163
                throw new InvalidOperationException("FilePath is empty");
164
            if (String.IsNullOrWhiteSpace(TempPath))
165
                throw new InvalidOperationException("TempPath is empty");
166
            Contract.EndContractBlock();
167

    
168
            //Copy the file to a temporary location. Changes will be made to the
169
            //temporary file, then it will replace the original file
170
            if (File.Exists(FilePath))
171
                File.Copy(FilePath, TempPath, true);
172

    
173
            //Set the size of the file to the size specified in the treehash
174
            //This will also create an empty file if the file doesn't exist                        
175
            
176
            
177
            SetFileSize(TempPath, ServerHash.Bytes);
178

    
179
            //Update the temporary file with the data from the blocks
180
            using (var stream = File.OpenWrite(TempPath))
181
            {
182
                foreach (var block in _blocks)
183
                {
184
                    var blockPath = block.Value;
185
                    var blockIndex = block.Key;
186
                    using (var blockStream = File.OpenRead(blockPath))
187
                    {                        
188
                        var offset = blockIndex*ServerHash.BlockSize;
189
                        stream.Seek(offset, SeekOrigin.Begin);
190
                        blockStream.CopyTo(stream);
191
                    }
192
                }
193
            }
194
            SwapFiles();
195

    
196
            ClearBlocks();
197
        }
198

    
199
        private void SwapFiles()
200
        {
201
            if (String.IsNullOrWhiteSpace(FilePath))
202
                throw new InvalidOperationException("FilePath is empty");
203
            if (String.IsNullOrWhiteSpace(TempPath))
204
                throw new InvalidOperationException("TempPath is empty");            
205
            Contract.EndContractBlock();
206

    
207
            if (File.Exists(FilePath))
208
                File.Replace(TempPath, FilePath, null, true);
209
            else
210
            {
211
                var targetDirectory = Path.GetDirectoryName(FilePath);
212
                if (!Directory.Exists(targetDirectory))
213
                    Directory.CreateDirectory(targetDirectory);
214
                File.Move(TempPath, FilePath);
215
            }
216
        }
217

    
218
        private void ClearBlocks()
219
        {
220
            //Get all the the block paths, orphan or not
221
            var paths= _blocks.Select(pair => pair.Value)
222
                          .Union(_orphanBlocks.Select(pair => pair.Value));
223
            foreach (var filePath in paths)
224
            {
225
                File.Delete(filePath);
226
            }
227

    
228
            File.Delete(TempPath);
229
            _blocks.Clear();
230
            _orphanBlocks.Clear();
231
        }
232

    
233
        //Change the file's size, possibly truncating or adding to it
234
        private  void SetFileSize(string filePath, long fileSize)
235
        {
236
            if (String.IsNullOrWhiteSpace(filePath))
237
                throw new ArgumentNullException("filePath");
238
            if (!Path.IsPathRooted(filePath))
239
                throw new ArgumentException("The filePath must be rooted", "filePath");
240
            if (fileSize < 0)
241
                throw new ArgumentOutOfRangeException("fileSize");
242
            Contract.EndContractBlock();
243

    
244
            using (var stream = File.Open(filePath, FileMode.OpenOrCreate, FileAccess.Write))
245
            {
246
                stream.SetLength(fileSize);
247
            }
248
        }
249

    
250
       /* //Check whether we should copy the local file to a temp path        
251
        private  bool ShouldCopy(string localPath, string tempPath)
252
        {
253
            //No need to copy if there is no file
254
            if (!File.Exists(localPath))
255
                return false;
256

    
257
            //If there is no temp file, go ahead and copy
258
            if (!File.Exists(tempPath))
259
                return true;
260

    
261
            //If there is a temp file and is newer than the actual file, don't copy
262
            var localLastWrite = File.GetLastWriteTime(localPath);
263
            var tempLastWrite = File.GetLastWriteTime(tempPath);
264

    
265
            //This could mean there is an interrupted download in progress
266
            return (tempLastWrite < localLastWrite);
267
        }*/
268

    
269

    
270
        public bool UseOrphan(int blockIndex, string blockHash)
271
        {
272
            string blockPath=null;
273
            if (_orphanBlocks.TryGetValue(blockHash,out blockPath))
274
            {
275
                _blocks[blockIndex] = blockPath;
276
                return true;
277
            }
278
            return false;
279
        }
280

    
281
        public Task StoreBlock(int blockIndex,byte[] buffer)
282
        {
283
            var blockPath = String.Format("{0}.{1:000000}", TempPath, blockIndex);
284
            _blocks[blockIndex] = blockPath;
285
            //Remove any orphan files
286
            if (File.Exists(blockPath))
287
                File.Delete(blockPath);
288

    
289
            return FileAsync.WriteAllBytes(blockPath, buffer);
290
        }
291

    
292
       
293

    
294
    }
295
}