Moved the pithos.db location to the same location as the user settings. This will...
[pithos-ms-client] / trunk / Pithos.Core / Agents / StatusAgent.cs
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 }