Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / StatusAgent.cs @ 74d78c90

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

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

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

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

    
53
            CleanupStaleStates();
54

    
55
        }        
56

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

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

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

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

    
90
            var source = new InPlaceConfigurationSource();                        
91
            source.Add(typeof (ActiveRecordBase), properties);
92
            source.SetDebugFlag(false);            
93
            return source;
94
        }
95

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

    
123
       
124

    
125
        public void Stop()
126
        {
127
            _persistenceAgent.Stop();            
128
        }
129
       
130

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

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

    
158
            var pairs = currentFiles.Union(deletedFiles);
159

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

    
192
       
193

    
194

    
195
        public string BlockHash { get; set; }
196

    
197
        public int BlockSize { get; set; }
198
        public void ChangeRoots(string oldPath, string newPath)
199
        {
200
            if (String.IsNullOrWhiteSpace(oldPath))
201
                throw new ArgumentNullException("oldPath");
202
            if (!Path.IsPathRooted(oldPath))
203
                throw new ArgumentException("oldPath must be an absolute path", "oldPath");
204
            if (string.IsNullOrWhiteSpace(newPath))
205
                throw new ArgumentNullException("newPath");
206
            if (!Path.IsPathRooted(newPath))
207
                throw new ArgumentException("newPath must be an absolute path", "newPath");
208
            Contract.EndContractBlock();
209

    
210
            FileState.ChangeRootPath(oldPath,newPath);
211

    
212
        }
213

    
214
        private PithosStatus _pithosStatus=PithosStatus.InSynch;       
215

    
216
        public void SetPithosStatus(PithosStatus status)
217
        {
218
            _pithosStatus = status;
219
        }
220

    
221
        public PithosStatus GetPithosStatus()
222
        {
223
            return _pithosStatus;
224
        }
225

    
226

    
227
        private string _pithosDataPath;
228

    
229
        public T GetStatus<T>(string path,Func<FileState,T> getter,T defaultValue )
230
        {
231
            if (String.IsNullOrWhiteSpace(path))
232
                throw new ArgumentNullException("path");
233
            if (!Path.IsPathRooted(path))
234
                throw new ArgumentException("path must be a rooted path", "path");
235
            if (getter == null)
236
                throw new ArgumentNullException("getter");
237
            Contract.EndContractBlock();
238

    
239

    
240
            try
241
            {                
242
                var state = FileState.FindByFilePath(path);
243
                return state == null ? defaultValue : getter(state);
244
            }
245
            catch (Exception exc)
246
            {
247
                Log.ErrorFormat(exc.ToString());
248
                return defaultValue;
249
            }
250
        }
251

    
252
        /// <summary>
253
        /// Sets the status of a file, creating a new FileState entry if one doesn't already exist.
254
        /// </summary>
255
        /// <param name="path"></param>
256
        /// <param name="setter"></param>
257
        public void SetStatus(string path,Action<FileState> setter)
258
        {
259
            if (String.IsNullOrWhiteSpace(path))
260
                throw new ArgumentNullException("path", "path can't be empty");
261
            if (setter==null)
262
                throw new ArgumentNullException("setter", "setter can't be empty");
263
            Contract.EndContractBlock();
264

    
265
            _persistenceAgent.Post(() =>
266
            {
267
                using (new SessionScope())
268
                {
269
                    var filePath = path.ToLower();
270
                    var state = FileState.FindByFilePath(filePath);
271
                    if (state != null)
272
                    {
273
                        setter(state);
274
                        state.Save();
275
                    }
276
                    else
277
                    {
278
                        state = new FileState {FilePath = filePath};
279
                        setter(state);
280
                        state.Save();
281
                    }                    
282
                }
283
            });
284
        }
285

    
286
        /// <summary>
287
        /// Sets the status of a file only if the file already exists
288
        /// </summary>
289
        /// <param name="path"></param>
290
        /// <param name="setter"></param>
291
        private void UpdateStatus(string path, Action<FileState> setter)
292
        {
293
            if (String.IsNullOrWhiteSpace(path))
294
                throw new ArgumentNullException("path");
295
            if (!Path.IsPathRooted(path))
296
                throw new ArgumentException("The path must be rooted", "path");
297
            if (setter == null)
298
                throw new ArgumentNullException("setter");
299
            Contract.EndContractBlock();
300

    
301
            Debug.Assert(!path.Contains(FolderConstants.CacheFolder));
302
            Debug.Assert(!path.EndsWith(".ignore"));
303

    
304
            if (String.IsNullOrWhiteSpace(path))
305
                throw new ArgumentNullException("path", "path can't be empty");
306

    
307
            if (setter == null)
308
                throw new ArgumentNullException("setter", "setter can't be empty");
309

    
310
            _persistenceAgent.Post(() =>
311
            {
312
                using (new SessionScope())
313
                {
314
                    var filePath = path.ToLower();
315

    
316
                    var state = FileState.FindByFilePath(filePath);
317
                    if (state == null)
318
                    {
319
                        Log.WarnFormat("[NOFILE] Unable to set status for {0}.", filePath);
320
                        return;
321
                    }
322
                    setter(state);
323
                    state.Save();
324
                }
325
                
326
            });
327
        }
328
        
329
        /// <summary>
330
        /// Sets the status of a specific state
331
        /// </summary>
332
        /// <param name="path"></param>
333
        /// <param name="setter"></param>
334
        private void UpdateStatus(Guid stateID, Action<FileState> setter)
335
        {
336
            if (setter == null)
337
                throw new ArgumentNullException("setter");
338
            Contract.EndContractBlock();
339

    
340

    
341
            _persistenceAgent.Post(() =>
342
            {
343
                using (new SessionScope())
344
                {
345
                    var state = FileState.Find(stateID);
346
                    if (state == null)
347
                    {
348
                        Log.WarnFormat("[NOFILE] Unable to set status for {0}.", stateID);
349
                        return;
350
                    }
351
                    setter(state);
352
                    state.Save();
353
                }
354
                
355
            });
356
        }
357

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

    
366
            try
367
            {
368
                var state = FileState.FindByFilePath(path);
369
                return state == null ? FileOverlayStatus.Unversioned : state.OverlayStatus;
370
            }
371
            catch (Exception exc)
372
            {
373
                Log.ErrorFormat(exc.ToString());
374
                return FileOverlayStatus.Unversioned;
375
            }
376
        }
377

    
378
        public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus)
379
        {
380
            if (String.IsNullOrWhiteSpace(path))
381
                throw new ArgumentNullException("path");
382
            if (!Path.IsPathRooted(path))
383
                throw new ArgumentException("The path must be rooted","path");
384
            Contract.EndContractBlock();
385

    
386
            SetStatus(path.ToLower(),s=>s.OverlayStatus=overlayStatus);
387
        }
388

    
389
        /*public void RemoveFileOverlayStatus(string path)
390
        {
391
            if (String.IsNullOrWhiteSpace(path))
392
                throw new ArgumentNullException("path");
393
            if (!Path.IsPathRooted(path))
394
                throw new ArgumentException("The path must be rooted", "path");
395
            Contract.EndContractBlock();
396

    
397
            _persistenceAgent.Post(() =>
398
                InnerRemoveFileOverlayStatus(path));
399
        }
400

    
401
        private static void InnerRemoveFileOverlayStatus(string path)
402
        {
403
            if (String.IsNullOrWhiteSpace(path))
404
                throw new ArgumentNullException("path");
405
            if (!Path.IsPathRooted(path))
406
                throw new ArgumentException("The path must be rooted", "path");
407
            Contract.EndContractBlock();
408

    
409
            FileState.DeleteByFilePath(path);            
410
        }*/
411

    
412
        public void RenameFileOverlayStatus(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
            _persistenceAgent.Post(() =>
425
                InnerRenameFileOverlayStatus(oldPath, newPath));
426
        }
427

    
428
        private static void InnerRenameFileOverlayStatus(string oldPath, string newPath)
429
        {
430
            if (String.IsNullOrWhiteSpace(oldPath))
431
                throw new ArgumentNullException("oldPath");
432
            if (!Path.IsPathRooted(oldPath))
433
                throw new ArgumentException("The oldPath must be rooted", "oldPath");
434
            if (String.IsNullOrWhiteSpace(newPath))
435
                throw new ArgumentNullException("newPath");
436
            if (!Path.IsPathRooted(newPath))
437
                throw new ArgumentException("The newPath must be rooted", "newPath");
438
            Contract.EndContractBlock();
439

    
440
            var state = FileState.FindByFilePath(oldPath);
441

    
442
            if (state == null)
443
            {
444
                Log.WarnFormat("[NOFILE] Unable to set status for {0}.", oldPath);
445
                return;
446
            }
447
            //NOTE: This will cause problems if path is used as a key in relationships
448
            state.FilePath = newPath;
449
            state.Update();
450
        }
451

    
452
        public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus)
453
        {
454
            if (String.IsNullOrWhiteSpace(path))
455
                throw new ArgumentNullException("path");
456
            if (!Path.IsPathRooted(path))
457
                throw new ArgumentException("The path must be rooted", "path");
458
            Contract.EndContractBlock();
459

    
460
            UpdateStatus(path.ToLower(),state=>
461
                                  {
462
                                      state.FileStatus = fileStatus;
463
                                      state.OverlayStatus = overlayStatus;
464
                                  });            
465
        }
466

    
467
        public void StoreInfo(string path,ObjectInfo objectInfo)
468
        {
469
            if (String.IsNullOrWhiteSpace(path))
470
                throw new ArgumentNullException("path");
471
            if (!Path.IsPathRooted(path))
472
                throw new ArgumentException("The path must be rooted", "path");            
473
            if (objectInfo == null)
474
                throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");
475
            Contract.EndContractBlock();
476

    
477
            _persistenceAgent.Post(() =>
478
            {
479
                var filePath = path.ToLower();
480
                //Load the existing files state and set its properties in one session            
481
                using (new SessionScope())
482
                {
483
                    //Forgetting to use a sessionscope results in two sessions being created, one by 
484
                    //FirstOrDefault and one by Save()
485
                    var state =FileState.FindByFilePath(filePath);
486
                    //Create a new empty state object if this is a new file
487
                    state = state ?? new FileState();
488

    
489
                    state.FilePath = filePath;
490
                    state.Checksum = objectInfo.Hash;
491
                    state.Version = objectInfo.Version;
492
                    state.VersionTimeStamp = objectInfo.VersionTimestamp;
493

    
494
                    state.FileStatus = FileStatus.Unchanged;
495
                    state.OverlayStatus = FileOverlayStatus.Normal;
496
                    
497
                    //Create a list of tags from the ObjectInfo's tag dictionary
498
                    //Make sure to bind each tag to its parent state so we don't have to save each tag separately
499
                    //state.Tags = (from pair in objectInfo.Tags
500
                    //                select
501
                    //                    new FileTag
502
                    //                        {
503
                    //                            FileState = state,
504
                    //                            Name = pair.Key,
505
                    //                            Value = pair.Value
506
                    //                        }
507
                    //            ).ToList();
508

    
509
                    //Do the save
510
                    state.Save();
511
                }
512
            });
513

    
514
        }
515

    
516
        
517
        public void SetFileStatus(string path, FileStatus status)
518
        {            
519
            UpdateStatus(path.ToLower(), state=>state.FileStatus = status);
520
        }
521

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

    
530
            var state = FileState.FindByFilePath(path);
531
            return (state==null)?FileStatus.Missing:state.FileStatus ;
532
        }
533

    
534
        public void ClearFileStatus(string path)
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
            //TODO:SHOULDN'T need both clear file status and remove overlay status
543
            _persistenceAgent.Post(() =>
544
            {
545
                using (new SessionScope())
546
                {
547
                    FileState.DeleteByFilePath(path);
548
                }
549
            });   
550
        }
551

    
552
        public void UpdateFileChecksum(string path, string checksum)
553
        {
554
            if (String.IsNullOrWhiteSpace(path))
555
                throw new ArgumentNullException("path");
556
            if (!Path.IsPathRooted(path))
557
                throw new ArgumentException("The path must be rooted", "path");            
558
            Contract.EndContractBlock();
559

    
560
            _persistenceAgent.Post(() =>
561
            {
562
                using (new SessionScope())
563
                {
564
                    var state = FileState.FindByFilePath(path);
565
                    if (state == null)
566
                    {
567
                        Log.WarnFormat("[NOFILE] Unable to set checkesum for {0}.", path);
568
                        return;
569
                    }
570
                    state.Checksum = checksum;
571
                    state.Update();
572
                }
573
            });
574
        }
575

    
576
    }
577

    
578
   
579
}