Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / StatusKeeper.cs @ 5ce54458

History | View | Annotate | Download (14.7 kB)

1
using System;
2
using System.Collections;
3
using System.Collections.Generic;
4
using System.ComponentModel.Composition;
5
using System.Diagnostics;
6
using System.Diagnostics.Contracts;
7
using System.IO;
8
using System.Linq;
9
using System.Linq.Expressions;
10
using System.Security.Cryptography;
11
using System.Text;
12
using System.Threading;
13
using System.Threading.Tasks;
14
using Castle.ActiveRecord;
15
using Castle.ActiveRecord.Framework.Config;
16
using NHibernate.Criterion;
17
using NHibernate.Impl;
18
using Pithos.Interfaces;
19
using Pithos.Network;
20

    
21
namespace Pithos.Core
22
{
23
    [Export(typeof(IStatusChecker)),Export(typeof(IStatusKeeper))]
24
    public class StatusKeeper:IStatusChecker,IStatusKeeper
25
    {
26
        [System.ComponentModel.Composition.Import]
27
        public IPithosSettings Settings { get; set; }
28

    
29
        private Agent<Action> _persistenceAgent;
30

    
31
        public StatusKeeper()
32
        {            
33
            var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
34
            _pithosDataPath = Path.Combine(appDataPath , "Pithos");
35

    
36
            if (!Directory.Exists(_pithosDataPath))
37
                Directory.CreateDirectory(_pithosDataPath);
38

    
39
            //File.Delete(Path.Combine(_pithosDataPath, "pithos.db"));
40

    
41
            var source = GetConfiguration(_pithosDataPath);
42
            ActiveRecordStarter.Initialize(source,typeof(FileState),typeof(FileTag));
43

    
44
            ;
45
            if (!File.Exists(Path.Combine(_pithosDataPath ,"pithos.db")))
46
                ActiveRecordStarter.CreateSchema();
47
            
48
            CleanupStaleStates();
49

    
50
        }
51

    
52
        private void CleanupStaleStates()
53
        {
54
            /*var stales = from state in FileState.Queryable
55
                         where state.FilePath.StartsWith(@"e:\pithos\fragments")
56
                         select state.Id;*/
57
            FileState.DeleteAll(@"FilePath like 'e:\pithos\fragments%'");
58
            ;
59
        }
60

    
61
        private static InPlaceConfigurationSource GetConfiguration(string pithosDbPath)
62
        {
63
            if (String.IsNullOrWhiteSpace(pithosDbPath))
64
                throw new ArgumentNullException("pithosDbPath");
65
            if (!Path.IsPathRooted(pithosDbPath))
66
                throw new ArgumentException("path must be a rooted path", "pithosDbPath");
67
            Contract.EndContractBlock();
68

    
69
            var properties = new Dictionary<string, string>
70
                                 {
71
                                     {"connection.driver_class", "NHibernate.Driver.SQLite20Driver"},
72
                                     {"dialect", "NHibernate.Dialect.SQLiteDialect"},
73
                                     {"connection.provider", "NHibernate.Connection.DriverConnectionProvider"},
74
                                     {
75
                                         "proxyfactory.factory_class",
76
                                         "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"
77
                                         },
78
                                 };
79

    
80
            var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3", pithosDbPath);
81
            properties.Add("connection.connection_string", connectionString);
82

    
83
            var source = new InPlaceConfigurationSource();
84

    
85
            source.Add(typeof (ActiveRecordBase), properties);
86
            return source;
87
        }
88

    
89
        public void StartProcessing(CancellationToken token)
90
        {
91
            _persistenceAgent = Agent<Action>.Start(queue =>
92
            {
93
                Action loop = null;
94
                loop = () =>
95
                {
96
                    var job = queue.Receive();
97
                    job.ContinueWith(t =>
98
                    {
99
                        var action = job.Result;
100
                        try
101
                        {
102
                            action();
103
                        }
104
                        catch (Exception ex)
105
                        {
106
                            Trace.TraceError("[ERROR] STATE \n{0}",ex);
107
                        }
108
                        queue.DoAsync(loop);
109
                    });
110
                };
111
                loop();
112
            });
113
            
114
        }
115

    
116
       
117

    
118
        public void Stop()
119
        {
120
            _persistenceAgent.Stop();            
121
        }
122
       
123

    
124
        public IEnumerable<string> StoreUnversionedFiles(ParallelQuery<string> paths)
125
        {
126
            var existingFiles = (from state in FileState.Queryable
127
                                 select state.FilePath.ToLower()).ToList();
128

    
129
            var newFiles = paths.Except(existingFiles.AsParallel());
130

    
131
            
132

    
133
            newFiles.ForAll(file =>
134
            {
135
                
136
                var createState = FileState.CreateForAsync(file)
137
                    .ContinueWith(state =>{                        
138
                    _persistenceAgent.Post(state.Result.Create);
139
                    return state.Result;
140
                });
141

    
142
                Func<Guid, Task<TreeHash>> treeBuilder = (stateId) => 
143
                    Signature.CalculateTreeHashAsync(file, BlockSize, BlockHash)
144
                    .ContinueWith(treeTask =>
145
                    {
146
                        var treeHash = treeTask.Result;
147
                        treeHash.FileId = stateId;
148
                        return treeHash;
149
                    });
150

    
151
                var createTree=createState.ContinueWith(stateTask => 
152
                    treeBuilder(stateTask.Result.Id))
153
                    .Unwrap();
154

    
155
                createTree.ContinueWith(treeTask =>
156
                    treeTask.Result.Save(_pithosDataPath));
157
            });
158

    
159
            return newFiles;
160

    
161
        }
162

    
163

    
164
        public string BlockHash { get; set; }
165

    
166
        public int BlockSize { get; set; }
167

    
168
        private PithosStatus _pithosStatus=PithosStatus.InSynch;       
169

    
170
        public void SetPithosStatus(PithosStatus status)
171
        {
172
            _pithosStatus = status;
173
        }
174

    
175
        public PithosStatus GetPithosStatus()
176
        {
177
            return _pithosStatus;
178
        }
179

    
180

    
181
        private string _pithosDataPath;
182

    
183
        public T GetStatus<T>(string path,Func<FileState,T> getter,T defaultValue )
184
        {
185
            if (String.IsNullOrWhiteSpace(path))
186
                throw new ArgumentNullException("path");
187
            if (!Path.IsPathRooted(path))
188
                throw new ArgumentException("path must be a rooted path", "path");
189
            Contract.EndContractBlock();
190

    
191

    
192
            try
193
            {                
194
                var state = FileState.Queryable.FirstOrDefault(s => s.FilePath == path.ToLower());
195
                return state == null ? defaultValue : getter(state);
196
            }
197
            catch (Exception exc)
198
            {
199
                Trace.TraceError(exc.ToString());
200
                return defaultValue;
201
            }
202
        }
203

    
204
        /// <summary>
205
        /// Sets the status of a file, creating a new FileState entry if one doesn't already exist.
206
        /// </summary>
207
        /// <param name="path"></param>
208
        /// <param name="setter"></param>
209
        public void SetStatus(string path,Action<FileState> setter)
210
        {
211
            if (String.IsNullOrWhiteSpace(path))
212
                throw new ArgumentNullException("path", "path can't be empty");
213

    
214
            if (setter==null)
215
                throw new ArgumentNullException("setter", "setter can't be empty");
216

    
217
            _persistenceAgent.Post(() =>
218
            {
219
                using (new SessionScope())
220
                {
221
                    var filePath = path.ToLower();
222
                    var state = FileState.Queryable.FirstOrDefault(s => s.FilePath == filePath);
223
                    if (state != null)
224
                    {
225
                        setter(state);
226
                        state.Save();
227
                    }
228
                    else
229
                    {
230
                        state = new FileState {FilePath = filePath};
231
                        setter(state);
232
                        state.Save();
233
                    }                    
234
                }
235
            });
236
        }
237

    
238
        /// <summary>
239
        /// Sets the status of a file only if the file already exists
240
        /// </summary>
241
        /// <param name="path"></param>
242
        /// <param name="setter"></param>
243
        private void UpdateStatus(string path, Action<FileState> setter)
244
        {
245
            Debug.Assert(!path.Contains("fragments"));
246
            Debug.Assert(!path.EndsWith(".ignore"));
247

    
248
            if (String.IsNullOrWhiteSpace(path))
249
                throw new ArgumentNullException("path", "path can't be empty");
250

    
251
            if (setter == null)
252
                throw new ArgumentNullException("setter", "setter can't be empty");
253

    
254
            _persistenceAgent.Post(() =>
255
            {
256
                using (new SessionScope())
257
                {
258
                    var filePath = path.ToLower();
259

    
260
                    var state = FileState.Queryable.FirstOrDefault(s => s.FilePath == filePath);
261
                    if (state == null)
262
                    {
263
                        Trace.TraceWarning("[NOFILE] Unable to set status for {0}.", filePath);
264
                        return;
265
                    }
266
                    setter(state);
267
                    state.Save();
268
                }
269
                
270
            });
271
        }
272

    
273
        public FileOverlayStatus GetFileOverlayStatus(string path)
274
        {
275
            try
276
            {
277
                var state = FileState.Queryable.FirstOrDefault(s => s.FilePath == path.ToLower());
278
                return state == null ? FileOverlayStatus.Unversioned : state.OverlayStatus;
279
            }
280
            catch (Exception exc)
281
            {
282
                Trace.TraceError(exc.ToString());
283
                return FileOverlayStatus.Unversioned;
284
            }
285
        }
286

    
287
        public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus)
288
        {
289
            SetStatus(path,s=>s.OverlayStatus=overlayStatus);
290
        }
291

    
292
        public void RemoveFileOverlayStatus(string path)
293
        {
294
            _persistenceAgent.Post(() =>
295
                InnerRemoveFileOverlayStatus(path));
296
        }
297

    
298
        private static void InnerRemoveFileOverlayStatus(string path)
299
        {
300
            FileState.DeleteAll(String.Format("FilePath = '{0}'",path));
301
        }
302

    
303
        public void RenameFileOverlayStatus(string oldPath, string newPath)
304
        {
305
            _persistenceAgent.Post(() =>
306
                InnerRenameFileOverlayStatus(oldPath, newPath));
307
        }
308

    
309
        private static void InnerRenameFileOverlayStatus(string oldPath, string newPath)
310
        {            
311
            var state = FileState.Queryable.FirstOrDefault(s => s.FilePath == oldPath);
312

    
313
            if (state == null)
314
            {
315
                Trace.TraceWarning("[NOFILE] Unable to set status for {0}.", oldPath);
316
                return;
317
            }
318
            //NOTE: This will cause problems if path is used as a key in relationships
319
            state.FilePath = newPath;
320
            state.Update();
321
        }
322

    
323
        public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus)
324
        {
325
            if (String.IsNullOrWhiteSpace(path))
326
                throw new ArgumentNullException("path", "path can't be empty");
327
            
328
            UpdateStatus(path,state=>
329
                                  {
330
                                      state.FileStatus = fileStatus;
331
                                      state.OverlayStatus = overlayStatus;
332
                                  });            
333
        }
334

    
335
        public void StoreInfo(string path,ObjectInfo objectInfo)
336
        {
337
            if (String.IsNullOrWhiteSpace(path))
338
                throw new ArgumentNullException("path", "path can't be empty");
339
            if (objectInfo==null)
340
                throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");
341

    
342

    
343
            _persistenceAgent.Post(() =>
344
            {
345
                var filePath = path.ToLower();
346
                //Load the existing files state and set its properties in one session            
347
                using (new SessionScope())
348
                {
349
                    //Forgetting to use a sessionscope results in two sessions being created, one by 
350
                    //FirstOrDefault and one by Save()
351
                    var state =
352
                        FileState.Queryable.FirstOrDefault(s => s.FilePath == filePath);
353
                    //Create a new empty state object if this is a new file
354
                    state = state ?? new FileState();
355

    
356
                    state.FilePath = filePath;
357
                    state.Checksum = objectInfo.Hash;
358
                    state.FileStatus = FileStatus.Unchanged;
359
                    state.OverlayStatus = FileOverlayStatus.Normal;
360
                    //Create a list of tags from the ObjectInfo's tag dictionary
361
                    //Make sure to bind each tag to its parent state so we don't have to save each tag separately
362
                    //state.Tags = (from pair in objectInfo.Tags
363
                    //                select
364
                    //                    new FileTag
365
                    //                        {
366
                    //                            FileState = state,
367
                    //                            Name = pair.Key,
368
                    //                            Value = pair.Value
369
                    //                        }
370
                    //            ).ToList();
371

    
372
                    //Do the save
373
                    state.Save();
374
                }
375
            });
376

    
377
        }
378

    
379
        
380
        public void SetFileStatus(string path, FileStatus status)
381
        {            
382
            UpdateStatus(path, state=>state.FileStatus = status);
383
        }
384

    
385
        public FileStatus GetFileStatus(string path)
386
        {            
387
            var state = FileState.Queryable.FirstOrDefault(s => s.FilePath == path.ToLower());
388
            return (state==null)?FileStatus.Missing:state.FileStatus ;
389
        }
390

    
391
        public void ClearFileStatus(string path)
392
        {
393
            //TODO:SHOULDN'T need both clear file status and remove overlay status
394
            _persistenceAgent.Post(()=>
395
                FileState.DeleteAll(new[] { path.ToLower() }));   
396
        }
397

    
398
        public void UpdateFileChecksum(string path, string checksum)
399
        {         
400
            _persistenceAgent.Post(()=>
401
            {
402
                using (new SessionScope())
403
                {
404
                    var state = FileState.Queryable.FirstOrDefault(s => s.FilePath == path);
405
                    if (state == null)
406
                    {
407
                        Trace.TraceWarning("[NOFILE] Unable to set checkesum for {0}.", path);
408
                        return;
409
                    }
410
                    state.Checksum = checksum;
411
                    state.Update();
412
                }
413
            });
414
        }
415

    
416
    }
417

    
418
   
419
}