Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / BlockUpdater.cs @ 73cdd135

History | View | Annotate | Download (9.6 kB)

1
using System;
2
using System.Collections.Concurrent;
3
using System.Collections.Generic;
4
using System.Diagnostics.Contracts;
5
using System.IO;
6
using System.Linq;
7
using System.Security.Cryptography;
8
using System.Text;
9
using System.Threading.Tasks;
10
using Pithos.Network;
11

    
12
namespace Pithos.Core.Agents
13
{
14
    class BlockUpdater
15
    {
16
        public string FilePath { get; private set; }
17
        public string RelativePath { get; private set; }
18

    
19
        public string CachePath { get; private set; }
20

    
21
        public TreeHash ServerHash { get; private set; }
22

    
23
        public string TempPath { get; private set; }
24

    
25
        public bool HasBlocks
26
        {
27
            get { return _blocks.Count>0; }            
28
        }
29

    
30
        readonly ConcurrentDictionary<int, string> _blocks = new ConcurrentDictionary<int, string>();
31
        readonly ConcurrentDictionary<string, string> _orphanBlocks = new ConcurrentDictionary<string, string>();
32

    
33
        [ContractInvariantMethod]
34
        private void Invariants()
35
        {
36
            Contract.Invariant(Path.IsPathRooted(CachePath));
37
            Contract.Invariant(Path.IsPathRooted(FilePath));
38
            Contract.Invariant(Path.IsPathRooted(TempPath));
39
            Contract.Invariant(!Path.IsPathRooted(RelativePath));
40
            Contract.Invariant(_blocks!=null);
41
            Contract.Invariant(_orphanBlocks!=null);
42
            Contract.Invariant(ServerHash!=null);
43
        }
44

    
45
        public BlockUpdater(string cachePath, string filePath, string relativePath,TreeHash serverHash)
46
        {   
47
            if (String.IsNullOrWhiteSpace(cachePath))
48
                throw new ArgumentNullException("cachePath");
49
            if (!Path.IsPathRooted(cachePath))
50
                throw new ArgumentException("The cachePath must be rooted", "cachePath");
51
            
52
            if (string.IsNullOrWhiteSpace(filePath))
53
                throw new ArgumentNullException("filePath");
54
            if (!Path.IsPathRooted(filePath))
55
                throw new ArgumentException("The filePath must be rooted", "filePath");
56
            
57
            if (string.IsNullOrWhiteSpace(relativePath))
58
                throw new ArgumentNullException("relativePath");
59
            if (Path.IsPathRooted(relativePath))
60
                throw new ArgumentException("The relativePath must NOT be rooted", "relativePath");
61

    
62
            if (serverHash == null)
63
                throw new ArgumentNullException("serverHash");
64
            Contract.EndContractBlock();
65

    
66
            CachePath=cachePath;
67
            FilePath = filePath;
68
            RelativePath=relativePath;
69
            ServerHash = serverHash;
70
            //The file will be stored in a temporary location while downloading with an extension .download
71
            TempPath = Path.Combine(CachePath, RelativePath + ".download");
72
            
73
            //Need to calculate the directory path because RelativePath may include folders
74
            var directoryPath = Path.GetDirectoryName(TempPath);            
75
            //directoryPath CAN be null if TempPath is a root path
76
            if (String.IsNullOrWhiteSpace(directoryPath))
77
                throw new ArgumentException("TempPath");
78
            //CachePath was absolute so directoryPath is absolute too
79
            Contract.Assume(Path.IsPathRooted(directoryPath));
80
            
81
            if (!Directory.Exists(directoryPath))
82
                Directory.CreateDirectory(directoryPath);
83

    
84
            LoadOrphans(directoryPath);
85
        }
86

    
87
        private void LoadOrphans(string directoryPath)
88
        {
89
            if (string.IsNullOrWhiteSpace(directoryPath))
90
                throw new ArgumentNullException("directoryPath");
91
            if (!Path.IsPathRooted(directoryPath))
92
                throw new ArgumentException("The directoryPath must be rooted", "directoryPath");
93
            if (ServerHash==null)
94
                throw new InvalidOperationException("ServerHash wasn't initialized");
95
            Contract.EndContractBlock();
96

    
97
            var fileNamename = Path.GetFileName(FilePath);
98
            var orphans = Directory.GetFiles(directoryPath, fileNamename + ".*");
99
            foreach (var orphan in orphans)
100
            {
101
                using (HashAlgorithm hasher = HashAlgorithm.Create(ServerHash.BlockHash))
102
                {
103
                    var buffer=File.ReadAllBytes(orphan);
104
                    //The server truncates nulls before calculating hashes, have to do the same
105
                    //Find the last non-null byte, starting from the end
106
                    var lastByteIndex = Array.FindLastIndex(buffer, buffer.Length-1, aByte => aByte != 0);
107
                    //lastByteIndex may be -1 if the file was empty. We don't want to use that block file
108
                    if (lastByteIndex >= 0)
109
                    {
110
                        var binHash = hasher.ComputeHash(buffer, 0, lastByteIndex);
111
                        var hash = binHash.ToHashString();
112
                        _orphanBlocks[hash] = orphan;
113
                    }
114
                }
115
            }
116
        }
117

    
118

    
119
        public void Commit()
120
        {
121
            if (String.IsNullOrWhiteSpace(FilePath))
122
                throw new InvalidOperationException("FilePath is empty");
123
            if (String.IsNullOrWhiteSpace(TempPath))
124
                throw new InvalidOperationException("TempPath is empty");
125
            Contract.EndContractBlock();
126

    
127
            //Copy the file to a temporary location. Changes will be made to the
128
            //temporary file, then it will replace the original file
129
            if (File.Exists(FilePath))
130
                File.Copy(FilePath, TempPath, true);
131

    
132
            //Set the size of the file to the size specified in the treehash
133
            //This will also create an empty file if the file doesn't exist                        
134
            
135
            
136
            SetFileSize(TempPath, ServerHash.Bytes);
137

    
138
            //Update the temporary file with the data from the blocks
139
            using (var stream = File.OpenWrite(TempPath))
140
            {
141
                foreach (var block in _blocks)
142
                {
143
                    var blockPath = block.Value;
144
                    var blockIndex = block.Key;
145
                    using (var blockStream = File.OpenRead(blockPath))
146
                    {                        
147
                        var offset = blockIndex*ServerHash.BlockSize;
148
                        stream.Seek(offset, SeekOrigin.Begin);
149
                        blockStream.CopyTo(stream);
150
                    }
151
                }
152
            }
153
            SwapFiles();
154

    
155
            ClearBlocks();
156
        }
157

    
158
        private void SwapFiles()
159
        {
160
            if (String.IsNullOrWhiteSpace(FilePath))
161
                throw new InvalidOperationException("FilePath is empty");
162
            if (String.IsNullOrWhiteSpace(TempPath))
163
                throw new InvalidOperationException("TempPath is empty");            
164
            Contract.EndContractBlock();
165

    
166
            if (File.Exists(FilePath))
167
                File.Replace(TempPath, FilePath, null, true);
168
            else
169
                File.Move(TempPath, FilePath);
170
        }
171

    
172
        private void ClearBlocks()
173
        {
174
            //Get all the the block paths, orphan or not
175
            var paths= _blocks.Select(pair => pair.Value)
176
                          .Union(_orphanBlocks.Select(pair => pair.Value));
177
            foreach (var filePath in paths)
178
            {
179
                File.Delete(filePath);
180
            }
181

    
182
            File.Delete(TempPath);
183
            _blocks.Clear();
184
            _orphanBlocks.Clear();
185
        }
186

    
187
        //Change the file's size, possibly truncating or adding to it
188
        private  void SetFileSize(string filePath, long fileSize)
189
        {
190
            if (String.IsNullOrWhiteSpace(filePath))
191
                throw new ArgumentNullException("filePath");
192
            if (!Path.IsPathRooted(filePath))
193
                throw new ArgumentException("The filePath must be rooted", "filePath");
194
            if (fileSize < 0)
195
                throw new ArgumentOutOfRangeException("fileSize");
196
            Contract.EndContractBlock();
197

    
198
            using (var stream = File.Open(filePath, FileMode.OpenOrCreate, FileAccess.Write))
199
            {
200
                stream.SetLength(fileSize);
201
            }
202
        }
203

    
204
       /* //Check whether we should copy the local file to a temp path        
205
        private  bool ShouldCopy(string localPath, string tempPath)
206
        {
207
            //No need to copy if there is no file
208
            if (!File.Exists(localPath))
209
                return false;
210

    
211
            //If there is no temp file, go ahead and copy
212
            if (!File.Exists(tempPath))
213
                return true;
214

    
215
            //If there is a temp file and is newer than the actual file, don't copy
216
            var localLastWrite = File.GetLastWriteTime(localPath);
217
            var tempLastWrite = File.GetLastWriteTime(tempPath);
218

    
219
            //This could mean there is an interrupted download in progress
220
            return (tempLastWrite < localLastWrite);
221
        }*/
222

    
223

    
224
        public bool UseOrphan(int blockIndex, string blockHash)
225
        {
226
            string blockPath=null;
227
            if (_orphanBlocks.TryGetValue(blockHash,out blockPath))
228
            {
229
                _blocks[blockIndex] = blockPath;
230
                return true;
231
            }
232
            return false;
233
        }
234

    
235
        public Task StoreBlock(int blockIndex,byte[] buffer)
236
        {
237
            var blockPath = String.Format("{0}.{1:000000}", TempPath, blockIndex);
238
            _blocks[blockIndex] = blockPath;
239
            //Remove any orphan files
240
            if (File.Exists(blockPath))
241
                File.Delete(blockPath);
242

    
243
            return FileAsync.WriteAllBytes(blockPath, buffer);
244
        }
245

    
246
       
247

    
248
    }
249
}