Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / StatusAgent.cs @ 618015f4

History | View | Annotate | Download (30 kB)

1
using System;
2
using System.Collections.Generic;
3
using System.ComponentModel.Composition;
4
using System.Data.SQLite;
5
using System.Diagnostics;
6
using System.Diagnostics.Contracts;
7
using System.IO;
8
using System.Linq;
9
using System.Text;
10
using System.Threading;
11
using System.Threading.Tasks;
12
using Castle.ActiveRecord;
13
using Castle.ActiveRecord.Framework.Config;
14
using Pithos.Interfaces;
15
using Pithos.Network;
16
using log4net;
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("StatusAgent");
30

    
31
        public StatusAgent()
32
        {            
33
            var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
34
            
35

    
36

    
37
            _pithosDataPath = Path.Combine(appDataPath , "GRNET");
38
            if (!Directory.Exists(_pithosDataPath))
39
                Directory.CreateDirectory(_pithosDataPath);
40

    
41
            var dbPath = Path.Combine(_pithosDataPath, "pithos.db");
42

    
43
            MigrateOldDb(dbPath, appDataPath);
44

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

    
49

    
50
            if (!File.Exists(dbPath))
51
                ActiveRecordStarter.CreateSchema();
52

    
53
            CreateTrigger();
54
            
55
        }
56

    
57

    
58
        private static void MigrateOldDb(string dbPath, string appDataPath)
59
        {
60
            Contract.Requires(!String.IsNullOrWhiteSpace(dbPath));
61
            Contract.Requires(!String.IsNullOrWhiteSpace(appDataPath));
62

    
63
            var oldDbPath = Path.Combine(appDataPath, "Pithos", "pithos.db");
64
            var oldDbInfo = new FileInfo(oldDbPath);
65
            if (oldDbInfo.Exists && !File.Exists(dbPath))
66
            {
67
                var oldDirectory = oldDbInfo.Directory;
68
                oldDbInfo.MoveTo(dbPath);                
69
                oldDirectory.Delete(true);
70
            }
71
        }
72

    
73
        private void CreateTrigger()
74
        {
75
            using (var connection = GetConnection())
76
            using (var triggerCommand = connection.CreateCommand())
77
            {
78
                var cmdText = new StringBuilder()
79
                    .AppendLine("CREATE TRIGGER IF NOT EXISTS update_last_modified UPDATE ON FileState FOR EACH ROW")
80
                    .AppendLine("BEGIN")
81
                    .AppendLine("UPDATE FileState SET Modified=datetime('now')  WHERE Id=old.Id;")
82
                    .AppendLine("END;")
83
                    .AppendLine("CREATE TRIGGER IF NOT EXISTS insert_last_modified INSERT ON FileState FOR EACH ROW")
84
                    .AppendLine("BEGIN")
85
                    .AppendLine("UPDATE FileState SET Modified=datetime('now')  WHERE Id=new.Id;")
86
                    .AppendLine("END;")
87
                    .ToString();
88
                triggerCommand.CommandText = cmdText;                
89
                triggerCommand.ExecuteNonQuery();
90
            }
91
        }
92

    
93

    
94
        private static InPlaceConfigurationSource GetConfiguration(string pithosDbPath)
95
        {
96
            if (String.IsNullOrWhiteSpace(pithosDbPath))
97
                throw new ArgumentNullException("pithosDbPath");
98
            if (!Path.IsPathRooted(pithosDbPath))
99
                throw new ArgumentException("path must be a rooted path", "pithosDbPath");
100
            Contract.EndContractBlock();
101

    
102
            var properties = new Dictionary<string, string>
103
                                 {
104
                                     {"connection.driver_class", "NHibernate.Driver.SQLite20Driver"},
105
                                     {"dialect", "NHibernate.Dialect.SQLiteDialect"},
106
                                     {"connection.provider", "NHibernate.Connection.DriverConnectionProvider"},
107
                                     {
108
                                         "proxyfactory.factory_class",
109
                                         "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"
110
                                         },
111
                                 };
112

    
113
            var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N", pithosDbPath);
114
            properties.Add("connection.connection_string", connectionString);
115

    
116
            var source = new InPlaceConfigurationSource();                        
117
            source.Add(typeof (ActiveRecordBase), properties);
118
            source.SetDebugFlag(false);            
119
            return source;
120
        }
121

    
122
        public void StartProcessing(CancellationToken token)
123
        {
124
            _persistenceAgent = Agent<Action>.Start(queue =>
125
            {
126
                Action loop = null;
127
                loop = () =>
128
                {
129
                    var job = queue.Receive();
130
                    job.ContinueWith(t =>
131
                    {
132
                        var action = job.Result;
133
                        try
134
                        {
135
                            action();
136
                        }
137
                        catch (SQLiteException ex)
138
                        {
139
                            Log.ErrorFormat("[ERROR] SQL \n{0}", ex);
140
                        }
141
                        catch (Exception ex)
142
                        {
143
                            Log.ErrorFormat("[ERROR] STATE \n{0}", ex);
144
                        }
145
// ReSharper disable AccessToModifiedClosure
146
                        queue.DoAsync(loop);
147
// ReSharper restore AccessToModifiedClosure
148
                    });
149
                };
150
                loop();
151
            });
152
            
153
        }
154

    
155
       
156

    
157
        public void Stop()
158
        {
159
            _persistenceAgent.Stop();            
160
        }
161
       
162

    
163
        public void ProcessExistingFiles(IEnumerable<FileInfo> existingFiles)
164
        {
165
            if(existingFiles  ==null)
166
                throw new ArgumentNullException("existingFiles");
167
            Contract.EndContractBlock();
168
            
169
            //Find new or matching files with a left join to the stored states
170
            var fileStates = FileState.Queryable;
171
            var currentFiles=from file in existingFiles
172
                      join state in fileStates on file.FullName.ToLower() equals state.FilePath.ToLower() into gs
173
                      from substate in gs.DefaultIfEmpty()
174
                               select new {File = file, State = substate};
175

    
176
            //To get the deleted files we must get the states that have no corresponding
177
            //files. 
178
            //We can't use the File.Exists method inside a query, so we get all file paths from the states
179
            var statePaths = (from state in fileStates
180
                             select new {state.Id, state.FilePath}).ToList();
181
            //and check each one
182
            var missingStates= (from path in statePaths
183
                                where !File.Exists(path.FilePath) && !Directory.Exists(path.FilePath)
184
                               select path.Id).ToList();
185
            //Finally, retrieve the states that correspond to the deleted files            
186
            var deletedFiles = from state in fileStates 
187
                        where missingStates.Contains(state.Id)
188
                        select new { File = default(FileInfo), State = state };
189

    
190
            var pairs = currentFiles.Union(deletedFiles);
191

    
192
            foreach(var pair in pairs)
193
            {
194
                var fileState = pair.State;
195
                var file = pair.File;
196
                if (fileState == null)
197
                {
198
                    //This is a new file
199
                    var fullPath = pair.File.FullName;
200
                    var createState = FileState.CreateForAsync(fullPath, BlockSize, BlockHash);
201
                    createState.ContinueWith(state => _persistenceAgent.Post(state.Result.Create));
202
                }                
203
                else if (file == null)
204
                {
205
                    //This file was deleted while we were down. We should mark it as deleted
206
                    //We have to go through UpdateStatus here because the state object we are using
207
                    //was created by a different ORM session.
208
                    _persistenceAgent.Post(()=> UpdateStatusDirect(fileState.Id, FileStatus.Deleted));                    
209
                }
210
                else
211
                {
212
                    //This file has a matching state. Need to check for possible changes
213
                    var hashString = file.CalculateHash(BlockSize,BlockHash);
214
                    //If the hashes don't match the file was changed
215
                    if (fileState.Checksum != hashString)
216
                    {
217
                        _persistenceAgent.Post(() => UpdateStatusDirect(fileState.Id, FileStatus.Modified));
218
                    }                    
219
                }
220
            };            
221
         
222
        }
223

    
224
        private int UpdateStatusDirect(Guid id, FileStatus status)
225
        {
226
            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
227
            {
228

    
229
                try
230
                {
231
                    
232
                    using (var connection = GetConnection())
233
                    using (
234
                        var command = new SQLiteCommand("update FileState set FileStatus= :fileStatus where Id = :id  ",
235
                                                        connection))
236
                    {                                                
237
                        command.Parameters.AddWithValue("fileStatus", status);
238

    
239
                        command.Parameters.AddWithValue("id", id);
240
                        
241
                        var affected = command.ExecuteNonQuery();
242
                        
243
                        return affected;
244
                    }
245

    
246
                }
247
                catch (Exception exc)
248
                {
249
                    Log.Error(exc.ToString());
250
                    throw;
251
                }
252
            }
253
        }
254
        
255
        private int UpdateStatusDirect(string path, FileStatus status)
256
        {
257
            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
258
            {
259

    
260
                try
261
                {
262

    
263
                    
264
                    using (var connection = GetConnection())
265
                    using (
266
                        var command =
267
                            new SQLiteCommand("update FileState set FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE",
268
                                              connection))
269
                    {
270

    
271

    
272
                        command.Parameters.AddWithValue("fileStatus", status);
273

    
274
                        command.Parameters.AddWithValue("path", path);
275
                        
276
                        var affected = command.ExecuteNonQuery();
277
                        return affected;
278
                    }
279
                }
280
                catch (Exception exc)
281
                {
282
                    Log.Error(exc.ToString());
283
                    throw;
284
                }
285
            }
286
        }
287

    
288
        private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
289
        {
290
            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
291
            {
292

    
293
                try
294
                {
295

    
296
                    
297
                    using (var connection = GetConnection())
298
                    using (
299
                        var command =
300
                            new SQLiteCommand(
301
                                "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE ",
302
                                connection))
303
                    {
304

    
305
                        command.Parameters.AddWithValue("path", absolutePath);
306
                        command.Parameters.AddWithValue("fileStatus", fileStatus);
307
                        command.Parameters.AddWithValue("overlayStatus", overlayStatus);
308
                        
309
                        var affected = command.ExecuteNonQuery();
310
                        return affected;
311
                    }
312
                }
313
                catch (Exception exc)
314
                {
315
                    Log.Error(exc.ToString());
316
                    throw;
317
                }
318
            }
319
        }
320
        
321

    
322

    
323
        public string BlockHash { get; set; }
324

    
325
        public int BlockSize { get; set; }
326
        public void ChangeRoots(string oldPath, string newPath)
327
        {
328
            if (String.IsNullOrWhiteSpace(oldPath))
329
                throw new ArgumentNullException("oldPath");
330
            if (!Path.IsPathRooted(oldPath))
331
                throw new ArgumentException("oldPath must be an absolute path", "oldPath");
332
            if (string.IsNullOrWhiteSpace(newPath))
333
                throw new ArgumentNullException("newPath");
334
            if (!Path.IsPathRooted(newPath))
335
                throw new ArgumentException("newPath must be an absolute path", "newPath");
336
            Contract.EndContractBlock();
337

    
338
            FileState.ChangeRootPath(oldPath,newPath);
339

    
340
        }
341

    
342
        private PithosStatus _pithosStatus=PithosStatus.InSynch;       
343

    
344
        public void SetPithosStatus(PithosStatus status)
345
        {
346
            _pithosStatus = status;
347
        }
348

    
349
        public PithosStatus GetPithosStatus()
350
        {
351
            return _pithosStatus;
352
        }
353

    
354

    
355
        private readonly string _pithosDataPath;
356

    
357

    
358
        public FileState GetStateByFilePath(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
                
369
                using (var connection = GetConnection())
370
                using (var command = new SQLiteCommand("select Id, FilePath, OverlayStatus,FileStatus ,Checksum   ,Version    ,VersionTimeStamp,IsShared   ,SharedBy   ,ShareWrite  from FileState where FilePath=:path COLLATE NOCASE", connection))
371
                {
372
                    
373
                    command.Parameters.AddWithValue("path", path);
374
                    
375
                    using (var reader = command.ExecuteReader())
376
                    {
377
                        if (reader.Read())
378
                        {
379
                            //var values = new object[reader.FieldCount];
380
                            //reader.GetValues(values);
381
                            var state = new FileState
382
                                            {
383
                                                Id = reader.GetGuid(0),
384
                                                FilePath = reader.IsDBNull(1)?"":reader.GetString(1),
385
                                                OverlayStatus =reader.IsDBNull(2)?FileOverlayStatus.Unversioned: (FileOverlayStatus) reader.GetInt64(2),
386
                                                FileStatus = reader.IsDBNull(3)?FileStatus.Missing:(FileStatus) reader.GetInt64(3),
387
                                                Checksum = reader.IsDBNull(4)?"":reader.GetString(4),
388
                                                Version = reader.IsDBNull(5)?default(long):reader.GetInt64(5),
389
                                                VersionTimeStamp = reader.IsDBNull(6)?default(DateTime):reader.GetDateTime(6),
390
                                                IsShared = !reader.IsDBNull(7) && reader.GetBoolean(7),
391
                                                SharedBy = reader.IsDBNull(8)?"":reader.GetString(8),
392
                                                ShareWrite = !reader.IsDBNull(9) && reader.GetBoolean(9)
393
                                            };
394
/*
395
                            var state = new FileState
396
                                            {
397
                                                Id = (Guid) values[0],
398
                                                FilePath = (string) values[1],
399
                                                OverlayStatus = (FileOverlayStatus) (long)values[2],
400
                                                FileStatus = (FileStatus) (long)values[3],
401
                                                Checksum = (string) values[4],
402
                                                Version = (long?) values[5],
403
                                                VersionTimeStamp = (DateTime?) values[6],
404
                                                IsShared = (long)values[7] == 1,
405
                                                SharedBy = (string) values[8],
406
                                                ShareWrite = (long)values[9] == 1
407
                                            };
408
*/
409
                            return state;
410
                        }
411
                        else
412
                        {
413
                            return null;
414
                        }
415

    
416
                    }                    
417
                }
418
            }
419
            catch (Exception exc)
420
            {
421
                Log.ErrorFormat(exc.ToString());
422
                throw;
423
            }            
424
        }
425

    
426
        public FileOverlayStatus GetFileOverlayStatus(string path)
427
        {
428
            if (String.IsNullOrWhiteSpace(path))
429
                throw new ArgumentNullException("path");
430
            if (!Path.IsPathRooted(path))
431
                throw new ArgumentException("The path must be rooted", "path");
432
            Contract.EndContractBlock();
433

    
434
            try
435
            {
436
                
437
                using (var connection = GetConnection())
438
                using (var command = new SQLiteCommand("select OverlayStatus from FileState where FilePath=:path  COLLATE NOCASE", connection))
439
                {
440
                    
441
                    command.Parameters.AddWithValue("path", path);
442
                    
443
                    var s = command.ExecuteScalar();
444
                    return (FileOverlayStatus) Convert.ToInt32(s);
445
                }
446
            }
447
            catch (Exception exc)
448
            {
449
                Log.ErrorFormat(exc.ToString());
450
                return FileOverlayStatus.Unversioned;
451
            }
452
        }
453

    
454
        private string GetConnectionString()
455
        {
456
            var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N;Pooling=True", _pithosDataPath);
457
            return connectionString;
458
        }
459

    
460
        private SQLiteConnection GetConnection()
461
        {
462
            var connectionString = GetConnectionString();
463
            var connection = new SQLiteConnection(connectionString);
464
            connection.Open();
465
            using(var cmd =connection.CreateCommand())
466
            {
467
                cmd.CommandText = "PRAGMA journal_mode=WAL";
468
                cmd.ExecuteNonQuery();
469
            }
470
            return connection;
471
        }
472

    
473
        public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus)
474
        {
475
            if (String.IsNullOrWhiteSpace(path))
476
                throw new ArgumentNullException("path");
477
            if (!Path.IsPathRooted(path))
478
                throw new ArgumentException("The path must be rooted","path");
479
            Contract.EndContractBlock();
480

    
481
            _persistenceAgent.Post(() => FileState.StoreOverlayStatus(path,overlayStatus));
482
        }
483

    
484
       /* public void RenameFileOverlayStatus(string oldPath, string newPath)
485
        {
486
            if (String.IsNullOrWhiteSpace(oldPath))
487
                throw new ArgumentNullException("oldPath");
488
            if (!Path.IsPathRooted(oldPath))
489
                throw new ArgumentException("The oldPath must be rooted", "oldPath");
490
            if (String.IsNullOrWhiteSpace(newPath))
491
                throw new ArgumentNullException("newPath");
492
            if (!Path.IsPathRooted(newPath))
493
                throw new ArgumentException("The newPath must be rooted", "newPath");
494
            Contract.EndContractBlock();
495

    
496
            _persistenceAgent.Post(() =>FileState.RenameState(oldPath, newPath));
497
        }*/
498

    
499
        public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus)
500
        {
501
            if (String.IsNullOrWhiteSpace(path))
502
                throw new ArgumentNullException("path");
503
            if (!Path.IsPathRooted(path))
504
                throw new ArgumentException("The path must be rooted", "path");
505
            Contract.EndContractBlock();
506

    
507
            Debug.Assert(!path.Contains(FolderConstants.CacheFolder));
508
            Debug.Assert(!path.EndsWith(".ignore"));
509

    
510
            _persistenceAgent.Post(() => UpdateStatusDirect(path, fileStatus, overlayStatus));
511
        }
512

    
513
/*
514
        public void StoreInfo(string path,ObjectInfo objectInfo)
515
        {
516
            if (String.IsNullOrWhiteSpace(path))
517
                throw new ArgumentNullException("path");
518
            if (!Path.IsPathRooted(path))
519
                throw new ArgumentException("The path must be rooted", "path");            
520
            if (objectInfo == null)
521
                throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");
522
            Contract.EndContractBlock();
523

    
524
            _persistenceAgent.Post(() =>
525
            {
526
                var filePath = path.ToLower();
527
                //Load the existing files state and set its properties in one session            
528
                using (new SessionScope())
529
                {
530
                    //Forgetting to use a sessionscope results in two sessions being created, one by 
531
                    //FirstOrDefault and one by Save()
532
                    var state =FileState.FindByFilePath(filePath);
533
                    
534
                    //Create a new empty state object if this is a new file
535
                    state = state ?? new FileState();
536

    
537
                    state.FilePath = filePath;
538
                    state.Checksum = objectInfo.Hash;
539
                    state.Version = objectInfo.Version;
540
                    state.VersionTimeStamp = objectInfo.VersionTimestamp;
541

    
542
                    state.FileStatus = FileStatus.Unchanged;
543
                    state.OverlayStatus = FileOverlayStatus.Normal;
544
                    
545
                  
546
                    //Do the save
547
                    state.Save();
548
                }
549
            });
550

    
551
        }
552
*/
553
        
554
        public void StoreInfo(string path, ObjectInfo objectInfo)
555
        {
556
            if (String.IsNullOrWhiteSpace(path))
557
                throw new ArgumentNullException("path");
558
            if (!Path.IsPathRooted(path))
559
                throw new ArgumentException("The path must be rooted", "path");
560
            if (objectInfo == null)
561
                throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");
562
            Contract.EndContractBlock();
563

    
564
            _persistenceAgent.Post(() => StoreInfoDirect(path, objectInfo));
565

    
566
        }
567

    
568
        private void StoreInfoDirect(string path, ObjectInfo objectInfo)
569
        {
570
            try
571
            {
572
                
573
                using (var connection = GetConnection())
574
                using (var command = new SQLiteCommand(connection))
575
                {
576
                    if (StateExists(path, connection))
577
                        command.CommandText =
578
                            "update FileState set FileStatus= :fileStatus where FilePath = :path  COLLATE NOCASE ";
579
                    else
580
                    {
581
                        command.CommandText =
582
                            "INSERT INTO FileState (Id,FilePath,Checksum,Version,VersionTimeStamp,FileStatus,OverlayStatus) VALUES (:id,:path,:checksum,:version,:versionTimeStamp,:fileStatus,:overlayStatus)";
583
                        command.Parameters.AddWithValue("id", Guid.NewGuid());
584
                    }
585

    
586
                    command.Parameters.AddWithValue("path", path);
587
                    command.Parameters.AddWithValue("checksum", objectInfo.Hash);
588
                    command.Parameters.AddWithValue("version", objectInfo.Version);
589
                    command.Parameters.AddWithValue("versionTimeStamp",
590
                                                    objectInfo.VersionTimestamp);
591
                    command.Parameters.AddWithValue("fileStatus", FileStatus.Unchanged);
592
                    command.Parameters.AddWithValue("overlayStatus",
593
                                                    FileOverlayStatus.Normal);
594

    
595
                    var affected = command.ExecuteNonQuery();
596
                    return;
597
                }
598
            }
599
            catch (Exception exc)
600
            {
601
                Log.Error(exc.ToString());
602
                throw;
603
            }
604
        }
605

    
606
        private bool StateExists(string filePath,SQLiteConnection connection)
607
        {
608
            using (var command = new SQLiteCommand("Select count(*) from FileState where FilePath=:path  COLLATE NOCASE", connection))
609
            {
610
                command.Parameters.AddWithValue("path", filePath);
611
                var result = command.ExecuteScalar();
612
                return ((long)result >= 1);
613
            }
614

    
615
        }
616

    
617
        public void SetFileStatus(string path, FileStatus status)
618
        {
619
            if (String.IsNullOrWhiteSpace(path))
620
                throw new ArgumentNullException("path");
621
            if (!Path.IsPathRooted(path))
622
                throw new ArgumentException("The path must be rooted", "path");
623
            Contract.EndContractBlock();
624
            
625
            _persistenceAgent.Post(() => UpdateStatusDirect(path, status));
626
        }
627

    
628
        public FileStatus GetFileStatus(string path)
629
        {
630
            if (String.IsNullOrWhiteSpace(path))
631
                throw new ArgumentNullException("path");
632
            if (!Path.IsPathRooted(path))
633
                throw new ArgumentException("The path must be rooted", "path");
634
            Contract.EndContractBlock();
635

    
636
            
637
            using (var connection = GetConnection())
638
            {
639
                var command = new SQLiteCommand("select FileStatus from FileState where FilePath=:path  COLLATE NOCASE", connection);
640
                command.Parameters.AddWithValue("path", path);
641
                
642
                var statusValue = command.ExecuteScalar();
643
                if (statusValue==null)
644
                    return FileStatus.Missing;
645
                return (FileStatus)Convert.ToInt32(statusValue);
646
            }
647
        }
648

    
649
        /// <summary>
650
        /// Deletes the status of the specified file
651
        /// </summary>
652
        /// <param name="path"></param>
653
        public void ClearFileStatus(string path)
654
        {
655
            if (String.IsNullOrWhiteSpace(path))
656
                throw new ArgumentNullException("path");
657
            if (!Path.IsPathRooted(path))
658
                throw new ArgumentException("The path must be rooted", "path");
659
            Contract.EndContractBlock();
660

    
661
            _persistenceAgent.Post(() => DeleteDirect(path));   
662
        }
663

    
664
        /// <summary>
665
        /// Deletes the status of the specified folder and all its contents
666
        /// </summary>
667
        /// <param name="path"></param>
668
        public void ClearFolderStatus(string path)
669
        {
670
            if (String.IsNullOrWhiteSpace(path))
671
                throw new ArgumentNullException("path");
672
            if (!Path.IsPathRooted(path))
673
                throw new ArgumentException("The path must be rooted", "path");
674
            Contract.EndContractBlock();
675

    
676
            _persistenceAgent.Post(() => DeleteFolderDirect(path));   
677
        }
678

    
679
        public IEnumerable<FileState> GetChildren(FileState fileState)
680
        {
681
            if (fileState == null)
682
                throw new ArgumentNullException("fileState");
683
            Contract.EndContractBlock();
684

    
685
            var children = from state in FileState.Queryable
686
                           where state.FilePath.StartsWith(fileState.FilePath + "\\")
687
                           select state;
688
            return children;
689
        }
690

    
691
        private int DeleteDirect(string filePath)
692
        {
693
            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))
694
            {
695

    
696
                try
697
                {
698

    
699
                    
700
                    using (var connection = GetConnection())
701
                    {
702
                        var command = new SQLiteCommand("delete from FileState where FilePath = :path  COLLATE NOCASE",
703
                                                        connection);
704

    
705
                        command.Parameters.AddWithValue("path", filePath);
706
                        
707
                        var affected = command.ExecuteNonQuery();
708
                        return affected;
709
                    }
710
                }
711
                catch (Exception exc)
712
                {
713
                    Log.Error(exc.ToString());
714
                    throw;
715
                }
716
            }
717
        }
718

    
719
        private int DeleteFolderDirect(string filePath)
720
        {
721
            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))
722
            {
723

    
724
                try
725
                {
726

    
727
                    
728
                    using (var connection = GetConnection())
729
                    {
730
                        var command = new SQLiteCommand("delete from FileState where FilePath = :path or FilePath like :path + '/%'  COLLATE NOCASE",
731
                                                        connection);
732

    
733
                        command.Parameters.AddWithValue("path", filePath);
734
                        
735
                        var affected = command.ExecuteNonQuery();
736
                        return affected;
737
                    }
738
                }
739
                catch (Exception exc)
740
                {
741
                    Log.Error(exc.ToString());
742
                    throw;
743
                }
744
            }
745
        }
746

    
747
        public void UpdateFileChecksum(string path, string checksum)
748
        {
749
            if (String.IsNullOrWhiteSpace(path))
750
                throw new ArgumentNullException("path");
751
            if (!Path.IsPathRooted(path))
752
                throw new ArgumentException("The path must be rooted", "path");            
753
            Contract.EndContractBlock();
754

    
755
            _persistenceAgent.Post(() => FileState.UpdateChecksum(path, checksum));
756
        }
757

    
758
    }
759

    
760
   
761
}