Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / StatusAgent.cs @ 4ec636f6

History | View | Annotate | Download (21.7 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 Pithos.Network;
14
using log4net;
15
using log4net.Appender;
16
using log4net.Config;
17
using log4net.Layout;
18

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

    
27
        private Agent<Action> _persistenceAgent;
28

    
29

    
30
        private static readonly ILog Log = LogManager.GetLogger("StatusAgent");
31

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

    
37
            if (!Directory.Exists(_pithosDataPath))
38
                Directory.CreateDirectory(_pithosDataPath);
39
            var source = GetConfiguration(_pithosDataPath);
40
            ActiveRecordStarter.Initialize(source,typeof(FileState),typeof(FileTag));
41
            ActiveRecordStarter.UpdateSchema();
42

    
43
            if (!File.Exists(Path.Combine(_pithosDataPath ,"pithos.db")))
44
                ActiveRecordStarter.CreateSchema();
45
        }        
46

    
47
        private static InPlaceConfigurationSource GetConfiguration(string pithosDbPath)
48
        {
49
            if (String.IsNullOrWhiteSpace(pithosDbPath))
50
                throw new ArgumentNullException("pithosDbPath");
51
            if (!Path.IsPathRooted(pithosDbPath))
52
                throw new ArgumentException("path must be a rooted path", "pithosDbPath");
53
            Contract.EndContractBlock();
54

    
55
            var properties = new Dictionary<string, string>
56
                                 {
57
                                     {"connection.driver_class", "NHibernate.Driver.SQLite20Driver"},
58
                                     {"dialect", "NHibernate.Dialect.SQLiteDialect"},
59
                                     {"connection.provider", "NHibernate.Connection.DriverConnectionProvider"},
60
                                     {
61
                                         "proxyfactory.factory_class",
62
                                         "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"
63
                                         },
64
                                 };
65

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

    
69
            var source = new InPlaceConfigurationSource();                        
70
            source.Add(typeof (ActiveRecordBase), properties);
71
            source.SetDebugFlag(false);            
72
            return source;
73
        }
74

    
75
        public void StartProcessing(CancellationToken token)
76
        {
77
            _persistenceAgent = Agent<Action>.Start(queue =>
78
            {
79
                Action loop = null;
80
                loop = () =>
81
                {
82
                    var job = queue.Receive();
83
                    job.ContinueWith(t =>
84
                    {
85
                        var action = job.Result;
86
                        try
87
                        {
88
                            action();
89
                        }
90
                        catch (Exception ex)
91
                        {
92
                            Log.ErrorFormat("[ERROR] STATE \n{0}",ex);
93
                        }
94
                        queue.DoAsync(loop);
95
                    });
96
                };
97
                loop();
98
            });
99
            
100
        }
101

    
102
       
103

    
104
        public void Stop()
105
        {
106
            _persistenceAgent.Stop();            
107
        }
108
       
109

    
110
        public void ProcessExistingFiles(IEnumerable<FileInfo> existingFiles)
111
        {
112
            if(existingFiles  ==null)
113
                throw new ArgumentNullException("existingFiles");
114
            Contract.EndContractBlock();
115
            
116
            //Find new or matching files with a left join to the stored states
117
            var fileStates = FileState.Queryable;
118
            var currentFiles=from file in existingFiles
119
                      join state in fileStates on file.FullName.ToLower() equals state.FilePath.ToLower() into gs
120
                      from substate in gs.DefaultIfEmpty()
121
                               select new {File = file, State = substate};
122

    
123
            //To get the deleted files we must get the states that have no corresponding
124
            //files. 
125
            //We can't use the File.Exists method inside a query, so we get all file paths from the states
126
            var statePaths = (from state in fileStates
127
                             select new {state.Id, state.FilePath}).ToList();
128
            //and check each one
129
            var missingStates= (from path in statePaths
130
                                where !File.Exists(path.FilePath) && !Directory.Exists(path.FilePath)
131
                               select path.Id).ToList();
132
            //Finally, retrieve the states that correspond to the deleted files            
133
            var deletedFiles = from state in fileStates 
134
                        where missingStates.Contains(state.Id)
135
                        select new { File = default(FileInfo), State = state };
136

    
137
            var pairs = currentFiles.Union(deletedFiles);
138

    
139
            Parallel.ForEach(pairs, pair =>
140
            {
141
                var fileState = pair.State;
142
                var file = pair.File;
143
                if (fileState == null)
144
                {
145
                    //This is a new file
146
                    var fullPath = pair.File.FullName.ToLower();
147
                    var createState = FileState.CreateForAsync(fullPath, this.BlockSize, this.BlockHash);
148
                    createState.ContinueWith(state => _persistenceAgent.Post(state.Result.Create));
149
                }                
150
                else if (file == null)
151
                {
152
                    //This file was deleted while we were down. We should mark it as deleted
153
                    //We have to go through UpdateStatus here because the state object we are using
154
                    //was created by a different ORM session.
155
                    UpdateStatus(fileState.Id,state=> state.FileStatus = FileStatus.Deleted);                    
156
                }
157
                else
158
                {
159
                    //This file has a matching state. Need to check for possible changes
160
                    var hashString = file.CalculateHash(BlockSize,BlockHash);
161
                    //If the hashes don't match the file was changed
162
                    if (fileState.Checksum != hashString)
163
                    {
164
                        UpdateStatus(fileState.Id, state => state.FileStatus = FileStatus.Modified);
165
                    }                    
166
                }
167
            });            
168
         
169
        }
170

    
171
       
172

    
173

    
174
        public string BlockHash { get; set; }
175

    
176
        public int BlockSize { get; set; }
177
        public void ChangeRoots(string oldPath, string newPath)
178
        {
179
            if (String.IsNullOrWhiteSpace(oldPath))
180
                throw new ArgumentNullException("oldPath");
181
            if (!Path.IsPathRooted(oldPath))
182
                throw new ArgumentException("oldPath must be an absolute path", "oldPath");
183
            if (string.IsNullOrWhiteSpace(newPath))
184
                throw new ArgumentNullException("newPath");
185
            if (!Path.IsPathRooted(newPath))
186
                throw new ArgumentException("newPath must be an absolute path", "newPath");
187
            Contract.EndContractBlock();
188

    
189
            FileState.ChangeRootPath(oldPath,newPath);
190

    
191
        }
192

    
193
        private PithosStatus _pithosStatus=PithosStatus.InSynch;       
194

    
195
        public void SetPithosStatus(PithosStatus status)
196
        {
197
            _pithosStatus = status;
198
        }
199

    
200
        public PithosStatus GetPithosStatus()
201
        {
202
            return _pithosStatus;
203
        }
204

    
205

    
206
        private string _pithosDataPath;
207

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

    
218

    
219
            try
220
            {                
221
                var state = FileState.FindByFilePath(path);
222
                return state == null ? defaultValue : getter(state);
223
            }
224
            catch (Exception exc)
225
            {
226
                Log.ErrorFormat(exc.ToString());
227
                return defaultValue;
228
            }
229
        }
230

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

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

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

    
280
            Debug.Assert(!path.Contains(FolderConstants.CacheFolder));
281
            Debug.Assert(!path.EndsWith(".ignore"));
282

    
283
            if (String.IsNullOrWhiteSpace(path))
284
                throw new ArgumentNullException("path", "path can't be empty");
285

    
286
            if (setter == null)
287
                throw new ArgumentNullException("setter", "setter can't be empty");
288

    
289
            _persistenceAgent.Post(() =>
290
            {
291
                using (new SessionScope())
292
                {
293
                    var filePath = path.ToLower();
294

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

    
320

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

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

    
346
            try
347
            {
348

    
349
                //var state = FileState.FindByFilePath(path);
350
                var st=from state in FileState.Queryable
351
                       where state.FilePath == path.ToLower()
352
                       select state.OverlayStatus; ;                
353
                return st.FirstOrDefault(); // state == null ? FileOverlayStatus.Unversioned : state.OverlayStatus;
354
            }
355
            catch (Exception exc)
356
            {
357
                Log.ErrorFormat(exc.ToString());
358
                return FileOverlayStatus.Unversioned;
359
            }
360
        }
361

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

    
370
            SetStatus(path.ToLower(),s=>s.OverlayStatus=overlayStatus);
371
        }
372

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

    
381
            _persistenceAgent.Post(() =>
382
                InnerRemoveFileOverlayStatus(path));
383
        }
384

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

    
393
            FileState.DeleteByFilePath(path);            
394
        }*/
395

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

    
408
            _persistenceAgent.Post(() =>
409
                InnerRenameFileOverlayStatus(oldPath, newPath));
410
        }
411

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

    
424
            var state = FileState.FindByFilePath(oldPath);
425

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

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

    
444
            checked HERE to fix conflicts
445

    
446
            UpdateStatus(path.ToLower(),state=>
447
                                  {
448
                                      state.FileStatus = fileStatus;
449
                                      state.OverlayStatus = overlayStatus;
450
                                  });            
451
        }
452

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

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

    
475
                    state.FilePath = filePath;
476
                    state.Checksum = objectInfo.Hash;
477
                    state.Version = objectInfo.Version;
478
                    state.VersionTimeStamp = objectInfo.VersionTimestamp;
479

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

    
495
                    //Do the save
496
                    state.Save();
497
                }
498
            });
499

    
500
        }
501

    
502
        
503
        public void SetFileStatus(string path, FileStatus status)
504
        {            
505
            UpdateStatus(path.ToLower(), state=>state.FileStatus = status);
506
        }
507

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

    
516
            var state = FileState.FindByFilePath(path);
517
            return (state==null)?FileStatus.Missing:state.FileStatus ;
518
        }
519

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

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

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

    
546
            _persistenceAgent.Post(() =>
547
            {
548
                using (new SessionScope())
549
                {
550
                    var state = FileState.FindByFilePath(path);
551
                    if (state == null)
552
                    {
553
                        Log.WarnFormat("[NOFILE] Unable to set checkesum for {0}.", path);
554
                        return;
555
                    }
556
                    state.Checksum = checksum;
557
                    state.Update();
558
                }
559
            });
560
        }
561

    
562
    }
563

    
564
   
565
}