Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / StatusAgent.cs @ cfed7823

History | View | Annotate | Download (21 kB)

1
using System;
2
using System.Collections.Generic;
3
using System.ComponentModel.Composition;
4
using System.Diagnostics;
5
using System.Diagnostics.Contracts;
6
using System.IO;
7
using System.Linq;
8
using System.Threading;
9
using System.Threading.Tasks;
10
using Castle.ActiveRecord;
11
using Castle.ActiveRecord.Framework.Config;
12
using Pithos.Interfaces;
13
using log4net;
14
using log4net.Appender;
15
using log4net.Config;
16
using log4net.Layout;
17

    
18
namespace Pithos.Core.Agents
19
{
20
    [Export(typeof(IStatusChecker)),Export(typeof(IStatusKeeper))]
21
    public class StatusAgent:IStatusChecker,IStatusKeeper
22
    {
23
        [System.ComponentModel.Composition.Import]
24
        public IPithosSettings Settings { get; set; }
25

    
26
        private Agent<Action> _persistenceAgent;
27

    
28

    
29
        private static readonly ILog log = LogManager.GetLogger(typeof(StatusAgent));
30

    
31
        public StatusAgent()
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

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

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

    
52
            CleanupStaleStates();
53

    
54
        }        
55

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

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

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

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

    
87
            var source = new InPlaceConfigurationSource();
88

    
89
            source.Add(typeof (ActiveRecordBase), properties);
90
            return source;
91
        }
92

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

    
120
       
121

    
122
        public void Stop()
123
        {
124
            _persistenceAgent.Stop();            
125
        }
126
       
127

    
128
        public void ProcessExistingFiles(IEnumerable<FileInfo> existingFiles)
129
        {
130
            if(existingFiles  ==null)
131
                throw new ArgumentNullException("existingFiles");
132
            Contract.EndContractBlock();
133
            
134
            //Find new or matching files with a left join to the stored states
135
            var fileStates = FileState.Queryable;
136
            var currentFiles=from file in existingFiles
137
                      join state in fileStates on file.FullName.ToLower() equals state.FilePath.ToLower() into gs
138
                      from substate in gs.DefaultIfEmpty()
139
                               select new {File = file, State = substate};
140

    
141
            //To get the deleted files we must get the states that have no corresponding
142
            //files. 
143
            //We can't use the File.Exists method inside a query, so we get all file paths from the states
144
            var statePaths = (from state in fileStates
145
                             select new {state.Id, state.FilePath}).ToList();
146
            //and check each one
147
            var missingStates= (from path in statePaths
148
                               where !File.Exists(path.FilePath)
149
                               select path.Id).ToList();
150
            //Finally, retrieve the states that correspond to the deleted files            
151
            var deletedFiles = from state in fileStates 
152
                        where missingStates.Contains(state.Id)
153
                        select new { File = default(FileInfo), State = state };
154

    
155
            var pairs = currentFiles.Union(deletedFiles);
156

    
157
            Parallel.ForEach(pairs, pair =>
158
            {
159
                var fileState = pair.State;
160
                var file = pair.File;
161
                if (fileState == null)
162
                {
163
                    //This is a new file
164
                    var fullPath = pair.File.FullName.ToLower();
165
                    var createState = FileState.CreateForAsync(fullPath, this.BlockSize, this.BlockHash);
166
                    createState.ContinueWith(state => _persistenceAgent.Post(state.Result.Create));
167
                }                
168
                else if (file == null)
169
                {
170
                    //This file was deleted while we were down. We should mark it as deleted
171
                    //We have to go through UpdateStatus here because the state object we are using
172
                    //was created by a different ORM session.
173
                    UpdateStatus(fileState.Id,state=> state.FileStatus = FileStatus.Deleted);                    
174
                }
175
                else
176
                {
177
                    //This file has a matching state. Need to check for possible changes
178
                    var hashString = file.CalculateHash(BlockSize,BlockHash);
179
                    //If the hashes don't match the file was changed
180
                    if (fileState.Checksum != hashString)
181
                    {
182
                        UpdateStatus(fileState.Id, state => state.FileStatus = FileStatus.Modified);
183
                    }                    
184
                }
185
            });            
186
         
187
        }
188

    
189
       
190

    
191

    
192
        public string BlockHash { get; set; }
193

    
194
        public int BlockSize { get; set; }
195

    
196
        private PithosStatus _pithosStatus=PithosStatus.InSynch;       
197

    
198
        public void SetPithosStatus(PithosStatus status)
199
        {
200
            _pithosStatus = status;
201
        }
202

    
203
        public PithosStatus GetPithosStatus()
204
        {
205
            return _pithosStatus;
206
        }
207

    
208

    
209
        private string _pithosDataPath;
210

    
211
        public T GetStatus<T>(string path,Func<FileState,T> getter,T defaultValue )
212
        {
213
            if (String.IsNullOrWhiteSpace(path))
214
                throw new ArgumentNullException("path");
215
            if (!Path.IsPathRooted(path))
216
                throw new ArgumentException("path must be a rooted path", "path");
217
            if (getter == null)
218
                throw new ArgumentNullException("getter");
219
            Contract.EndContractBlock();
220

    
221

    
222
            try
223
            {                
224
                var state = FileState.FindByFilePath(path);
225
                return state == null ? defaultValue : getter(state);
226
            }
227
            catch (Exception exc)
228
            {
229
                Trace.TraceError(exc.ToString());
230
                return defaultValue;
231
            }
232
        }
233

    
234
        /// <summary>
235
        /// Sets the status of a file, creating a new FileState entry if one doesn't already exist.
236
        /// </summary>
237
        /// <param name="path"></param>
238
        /// <param name="setter"></param>
239
        public void SetStatus(string path,Action<FileState> setter)
240
        {
241
            if (String.IsNullOrWhiteSpace(path))
242
                throw new ArgumentNullException("path", "path can't be empty");
243
            if (setter==null)
244
                throw new ArgumentNullException("setter", "setter can't be empty");
245
            Contract.EndContractBlock();
246

    
247
            _persistenceAgent.Post(() =>
248
            {
249
                using (new SessionScope())
250
                {
251
                    var filePath = path.ToLower();
252
                    var state = FileState.FindByFilePath(filePath);
253
                    if (state != null)
254
                    {
255
                        setter(state);
256
                        state.Save();
257
                    }
258
                    else
259
                    {
260
                        state = new FileState {FilePath = filePath};
261
                        setter(state);
262
                        state.Save();
263
                    }                    
264
                }
265
            });
266
        }
267

    
268
        /// <summary>
269
        /// Sets the status of a file only if the file already exists
270
        /// </summary>
271
        /// <param name="path"></param>
272
        /// <param name="setter"></param>
273
        private void UpdateStatus(string path, Action<FileState> setter)
274
        {
275
            if (String.IsNullOrWhiteSpace(path))
276
                throw new ArgumentNullException("path");
277
            if (!Path.IsPathRooted(path))
278
                throw new ArgumentException("The path must be rooted", "path");
279
            if (setter == null)
280
                throw new ArgumentNullException("setter");
281
            Contract.EndContractBlock();
282

    
283
            Debug.Assert(!path.Contains("fragments"));
284
            Debug.Assert(!path.EndsWith(".ignore"));
285

    
286
            if (String.IsNullOrWhiteSpace(path))
287
                throw new ArgumentNullException("path", "path can't be empty");
288

    
289
            if (setter == null)
290
                throw new ArgumentNullException("setter", "setter can't be empty");
291

    
292
            _persistenceAgent.Post(() =>
293
            {
294
                using (new SessionScope())
295
                {
296
                    var filePath = path.ToLower();
297

    
298
                    var state = FileState.FindByFilePath(filePath);
299
                    if (state == null)
300
                    {
301
                        Trace.TraceWarning("[NOFILE] Unable to set status for {0}.", filePath);
302
                        return;
303
                    }
304
                    setter(state);
305
                    state.Save();
306
                }
307
                
308
            });
309
        }
310
        
311
        /// <summary>
312
        /// Sets the status of a specific state
313
        /// </summary>
314
        /// <param name="path"></param>
315
        /// <param name="setter"></param>
316
        private void UpdateStatus(Guid stateID, Action<FileState> setter)
317
        {
318
            if (setter == null)
319
                throw new ArgumentNullException("setter");
320
            Contract.EndContractBlock();
321

    
322

    
323
            _persistenceAgent.Post(() =>
324
            {
325
                using (new SessionScope())
326
                {
327
                    var state = FileState.Find(stateID);
328
                    if (state == null)
329
                    {
330
                        Trace.TraceWarning("[NOFILE] Unable to set status for {0}.", stateID);
331
                        return;
332
                    }
333
                    setter(state);
334
                    state.Save();
335
                }
336
                
337
            });
338
        }
339

    
340
        public FileOverlayStatus GetFileOverlayStatus(string path)
341
        {
342
            if (String.IsNullOrWhiteSpace(path))
343
                throw new ArgumentNullException("path");
344
            if (!Path.IsPathRooted(path))
345
                throw new ArgumentException("The path must be rooted", "path");
346
            Contract.EndContractBlock();
347

    
348
            try
349
            {
350
                var state = FileState.FindByFilePath(path);
351
                return state == null ? FileOverlayStatus.Unversioned : state.OverlayStatus;
352
            }
353
            catch (Exception exc)
354
            {
355
                Trace.TraceError(exc.ToString());
356
                return FileOverlayStatus.Unversioned;
357
            }
358
        }
359

    
360
        public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus)
361
        {
362
            if (String.IsNullOrWhiteSpace(path))
363
                throw new ArgumentNullException("path");
364
            if (!Path.IsPathRooted(path))
365
                throw new ArgumentException("The path must be rooted","path");
366
            Contract.EndContractBlock();
367

    
368
            SetStatus(path.ToLower(),s=>s.OverlayStatus=overlayStatus);
369
        }
370

    
371
        /*public void RemoveFileOverlayStatus(string path)
372
        {
373
            if (String.IsNullOrWhiteSpace(path))
374
                throw new ArgumentNullException("path");
375
            if (!Path.IsPathRooted(path))
376
                throw new ArgumentException("The path must be rooted", "path");
377
            Contract.EndContractBlock();
378

    
379
            _persistenceAgent.Post(() =>
380
                InnerRemoveFileOverlayStatus(path));
381
        }
382

    
383
        private static void InnerRemoveFileOverlayStatus(string path)
384
        {
385
            if (String.IsNullOrWhiteSpace(path))
386
                throw new ArgumentNullException("path");
387
            if (!Path.IsPathRooted(path))
388
                throw new ArgumentException("The path must be rooted", "path");
389
            Contract.EndContractBlock();
390

    
391
            FileState.DeleteByFilePath(path);            
392
        }*/
393

    
394
        public void RenameFileOverlayStatus(string oldPath, string newPath)
395
        {
396
            if (String.IsNullOrWhiteSpace(oldPath))
397
                throw new ArgumentNullException("oldPath");
398
            if (!Path.IsPathRooted(oldPath))
399
                throw new ArgumentException("The oldPath must be rooted", "oldPath");
400
            if (String.IsNullOrWhiteSpace(newPath))
401
                throw new ArgumentNullException("newPath");
402
            if (!Path.IsPathRooted(newPath))
403
                throw new ArgumentException("The newPath must be rooted", "newPath");
404
            Contract.EndContractBlock();
405

    
406
            _persistenceAgent.Post(() =>
407
                InnerRenameFileOverlayStatus(oldPath, newPath));
408
        }
409

    
410
        private static void InnerRenameFileOverlayStatus(string oldPath, string newPath)
411
        {
412
            if (String.IsNullOrWhiteSpace(oldPath))
413
                throw new ArgumentNullException("oldPath");
414
            if (!Path.IsPathRooted(oldPath))
415
                throw new ArgumentException("The oldPath must be rooted", "oldPath");
416
            if (String.IsNullOrWhiteSpace(newPath))
417
                throw new ArgumentNullException("newPath");
418
            if (!Path.IsPathRooted(newPath))
419
                throw new ArgumentException("The newPath must be rooted", "newPath");
420
            Contract.EndContractBlock();
421

    
422
            var state = FileState.FindByFilePath(oldPath);
423

    
424
            if (state == null)
425
            {
426
                Trace.TraceWarning("[NOFILE] Unable to set status for {0}.", oldPath);
427
                return;
428
            }
429
            //NOTE: This will cause problems if path is used as a key in relationships
430
            state.FilePath = newPath;
431
            state.Update();
432
        }
433

    
434
        public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus)
435
        {
436
            if (String.IsNullOrWhiteSpace(path))
437
                throw new ArgumentNullException("path");
438
            if (!Path.IsPathRooted(path))
439
                throw new ArgumentException("The path must be rooted", "path");
440
            Contract.EndContractBlock();
441

    
442
            UpdateStatus(path.ToLower(),state=>
443
                                  {
444
                                      state.FileStatus = fileStatus;
445
                                      state.OverlayStatus = overlayStatus;
446
                                  });            
447
        }
448

    
449
        public void StoreInfo(string path,ObjectInfo objectInfo)
450
        {
451
            if (String.IsNullOrWhiteSpace(path))
452
                throw new ArgumentNullException("path");
453
            if (!Path.IsPathRooted(path))
454
                throw new ArgumentException("The path must be rooted", "path");            
455
            if (objectInfo == null)
456
                throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");
457
            Contract.EndContractBlock();
458

    
459
            _persistenceAgent.Post(() =>
460
            {
461
                var filePath = path.ToLower();
462
                //Load the existing files state and set its properties in one session            
463
                using (new SessionScope())
464
                {
465
                    //Forgetting to use a sessionscope results in two sessions being created, one by 
466
                    //FirstOrDefault and one by Save()
467
                    var state =FileState.FindByFilePath(filePath);
468
                    //Create a new empty state object if this is a new file
469
                    state = state ?? new FileState();
470

    
471
                    state.FilePath = filePath;
472
                    state.Checksum = objectInfo.Hash;
473
                    state.Version = objectInfo.Version;
474
                    state.VersionTimeStamp = objectInfo.VersionTimestamp;
475

    
476
                    state.FileStatus = FileStatus.Unchanged;
477
                    state.OverlayStatus = FileOverlayStatus.Normal;
478
                    
479
                    //Create a list of tags from the ObjectInfo's tag dictionary
480
                    //Make sure to bind each tag to its parent state so we don't have to save each tag separately
481
                    //state.Tags = (from pair in objectInfo.Tags
482
                    //                select
483
                    //                    new FileTag
484
                    //                        {
485
                    //                            FileState = state,
486
                    //                            Name = pair.Key,
487
                    //                            Value = pair.Value
488
                    //                        }
489
                    //            ).ToList();
490

    
491
                    //Do the save
492
                    state.Save();
493
                }
494
            });
495

    
496
        }
497

    
498
        
499
        public void SetFileStatus(string path, FileStatus status)
500
        {            
501
            UpdateStatus(path.ToLower(), state=>state.FileStatus = status);
502
        }
503

    
504
        public FileStatus GetFileStatus(string path)
505
        {
506
            if (String.IsNullOrWhiteSpace(path))
507
                throw new ArgumentNullException("path");
508
            if (!Path.IsPathRooted(path))
509
                throw new ArgumentException("The path must be rooted", "path");
510
            Contract.EndContractBlock();
511

    
512
            var state = FileState.FindByFilePath(path);
513
            return (state==null)?FileStatus.Missing:state.FileStatus ;
514
        }
515

    
516
        public void ClearFileStatus(string path)
517
        {
518
            if (String.IsNullOrWhiteSpace(path))
519
                throw new ArgumentNullException("path");
520
            if (!Path.IsPathRooted(path))
521
                throw new ArgumentException("The path must be rooted", "path");
522
            Contract.EndContractBlock();
523

    
524
            //TODO:SHOULDN'T need both clear file status and remove overlay status
525
            _persistenceAgent.Post(() =>
526
            {
527
                using (new SessionScope())
528
                {
529
                    FileState.DeleteByFilePath(path);
530
                }
531
            });   
532
        }
533

    
534
        public void UpdateFileChecksum(string path, string checksum)
535
        {
536
            if (String.IsNullOrWhiteSpace(path))
537
                throw new ArgumentNullException("path");
538
            if (!Path.IsPathRooted(path))
539
                throw new ArgumentException("The path must be rooted", "path");            
540
            Contract.EndContractBlock();
541

    
542
            _persistenceAgent.Post(() =>
543
            {
544
                using (new SessionScope())
545
                {
546
                    var state = FileState.FindByFilePath(path);
547
                    if (state == null)
548
                    {
549
                        Trace.TraceWarning("[NOFILE] Unable to set checkesum for {0}.", path);
550
                        return;
551
                    }
552
                    state.Checksum = checksum;
553
                    state.Update();
554
                }
555
            });
556
        }
557

    
558
    }
559

    
560
   
561
}