Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / BlockUpdater.cs @ 922c6aff

History | View | Annotate | Download (11.8 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.Reflection;
49
using System.Security.Cryptography;
50
using System.Text;
51
using System.Threading.Tasks;
52
using Pithos.Network;
53

    
54
namespace Pithos.Core.Agents
55
{
56
    class BlockUpdater
57
    {
58
        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
59

    
60
        public string FilePath { get; private set; }
61
        public string RelativePath { get; private set; }
62

    
63
        public string CachePath { get; private set; }
64

    
65
        public TreeHash ServerHash { get; private set; }
66

    
67
        public string TempPath { get; private set; }
68

    
69
        public bool HasBlocks
70
        {
71
            get { return _blocks.Count>0; }            
72
        }
73

    
74
        readonly ConcurrentDictionary<int, string> _blocks = new ConcurrentDictionary<int, string>();
75
        readonly ConcurrentDictionary<string, string> _orphanBlocks = new ConcurrentDictionary<string, string>();
76

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

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

    
106
            if (serverHash == null)
107
                throw new ArgumentNullException("serverHash");
108
            Contract.EndContractBlock();
109

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

    
128
            LoadOrphans(directoryPath);
129
        }
130

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

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

    
162

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

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

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

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

    
199
            ClearBlocks();
200
        }
201

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

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

    
221
        private void ClearBlocks()
222
        {
223
            if (Log.IsDebugEnabled)
224
                Log.DebugFormat("Clearing blocks for {0}",this.FilePath);
225
            //Get all the the block paths, orphan or not
226
            var paths= _blocks.Select(pair => pair.Value)
227
                          .Union(_orphanBlocks.Select(pair => pair.Value));
228
            foreach (var filePath in paths)
229
            {
230
                File.Delete(filePath);
231
            }
232

    
233
            File.Delete(TempPath);
234
            _blocks.Clear();
235
            _orphanBlocks.Clear();
236
        }
237

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

    
249
            using (var stream = File.Open(filePath, FileMode.OpenOrCreate, FileAccess.Write))
250
            {
251
                stream.SetLength(fileSize);
252
            }
253
        }
254

    
255
       /* //Check whether we should copy the local file to a temp path        
256
        private  bool ShouldCopy(string localPath, string tempPath)
257
        {
258
            //No need to copy if there is no file
259
            if (!File.Exists(localPath))
260
                return false;
261

    
262
            //If there is no temp file, go ahead and copy
263
            if (!File.Exists(tempPath))
264
                return true;
265

    
266
            //If there is a temp file and is newer than the actual file, don't copy
267
            var localLastWrite = File.GetLastWriteTime(localPath);
268
            var tempLastWrite = File.GetLastWriteTime(tempPath);
269

    
270
            //This could mean there is an interrupted download in progress
271
            return (tempLastWrite < localLastWrite);
272
        }*/
273

    
274

    
275
        public bool UseOrphan(int blockIndex, string blockHash)
276
        {
277
            string blockPath=null;
278
            if (_orphanBlocks.TryGetValue(blockHash,out blockPath))
279
            {
280
                _blocks[blockIndex] = blockPath;
281
                return true;
282
            }
283
            return false;
284
        }
285

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

    
294
            return FileAsync.WriteAllBytes(blockPath, buffer);
295
        }
296

    
297
       
298

    
299
    }
300
}