Extracted cloud delete functionality to a separate DeleteAgent
[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             _pithosDataPath = Path.Combine(appDataPath , "Pithos");
35
36             if (!Directory.Exists(_pithosDataPath))
37                 Directory.CreateDirectory(_pithosDataPath);
38             var source = GetConfiguration(_pithosDataPath);
39             ActiveRecordStarter.Initialize(source,typeof(FileState),typeof(FileTag));
40             ActiveRecordStarter.UpdateSchema();
41             
42
43             if (!File.Exists(Path.Combine(_pithosDataPath ,"pithos.db")))
44                 ActiveRecordStarter.CreateSchema();
45
46             CreateTrigger();
47             
48         }
49
50         private void CreateTrigger()
51         {
52             using (var connection = GetConnection())
53             using (var triggerCommand = connection.CreateCommand())
54             {
55                 var cmdText = new StringBuilder()
56                     .AppendLine("CREATE TRIGGER IF NOT EXISTS update_last_modified UPDATE ON FileState FOR EACH ROW")
57                     .AppendLine("BEGIN")
58                     .AppendLine("UPDATE FileState SET Modified=datetime('now')  WHERE Id=old.Id;")
59                     .AppendLine("END;")
60                     .AppendLine("CREATE TRIGGER IF NOT EXISTS insert_last_modified INSERT ON FileState FOR EACH ROW")
61                     .AppendLine("BEGIN")
62                     .AppendLine("UPDATE FileState SET Modified=datetime('now')  WHERE Id=new.Id;")
63                     .AppendLine("END;")
64                     .ToString();
65                 triggerCommand.CommandText = cmdText;                
66                 triggerCommand.ExecuteNonQuery();
67             }
68         }
69
70
71         private static InPlaceConfigurationSource GetConfiguration(string pithosDbPath)
72         {
73             if (String.IsNullOrWhiteSpace(pithosDbPath))
74                 throw new ArgumentNullException("pithosDbPath");
75             if (!Path.IsPathRooted(pithosDbPath))
76                 throw new ArgumentException("path must be a rooted path", "pithosDbPath");
77             Contract.EndContractBlock();
78
79             var properties = new Dictionary<string, string>
80                                  {
81                                      {"connection.driver_class", "NHibernate.Driver.SQLite20Driver"},
82                                      {"dialect", "NHibernate.Dialect.SQLiteDialect"},
83                                      {"connection.provider", "NHibernate.Connection.DriverConnectionProvider"},
84                                      {
85                                          "proxyfactory.factory_class",
86                                          "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"
87                                          },
88                                  };
89
90             var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N", pithosDbPath);
91             properties.Add("connection.connection_string", connectionString);
92
93             var source = new InPlaceConfigurationSource();                        
94             source.Add(typeof (ActiveRecordBase), properties);
95             source.SetDebugFlag(false);            
96             return source;
97         }
98
99         public void StartProcessing(CancellationToken token)
100         {
101             _persistenceAgent = Agent<Action>.Start(queue =>
102             {
103                 Action loop = null;
104                 loop = () =>
105                 {
106                     var job = queue.Receive();
107                     job.ContinueWith(t =>
108                     {
109                         var action = job.Result;
110                         try
111                         {
112                             action();
113                         }
114                         catch (SQLiteException ex)
115                         {
116                             Log.ErrorFormat("[ERROR] SQL \n{0}", ex);
117                         }
118                         catch (Exception ex)
119                         {
120                             Log.ErrorFormat("[ERROR] STATE \n{0}", ex);
121                         }
122 // ReSharper disable AccessToModifiedClosure
123                         queue.DoAsync(loop);
124 // ReSharper restore AccessToModifiedClosure
125                     });
126                 };
127                 loop();
128             });
129             
130         }
131
132        
133
134         public void Stop()
135         {
136             _persistenceAgent.Stop();            
137         }
138        
139
140         public void ProcessExistingFiles(IEnumerable<FileInfo> existingFiles)
141         {
142             if(existingFiles  ==null)
143                 throw new ArgumentNullException("existingFiles");
144             Contract.EndContractBlock();
145             
146             //Find new or matching files with a left join to the stored states
147             var fileStates = FileState.Queryable;
148             var currentFiles=from file in existingFiles
149                       join state in fileStates on file.FullName.ToLower() equals state.FilePath.ToLower() into gs
150                       from substate in gs.DefaultIfEmpty()
151                                select new {File = file, State = substate};
152
153             //To get the deleted files we must get the states that have no corresponding
154             //files. 
155             //We can't use the File.Exists method inside a query, so we get all file paths from the states
156             var statePaths = (from state in fileStates
157                              select new {state.Id, state.FilePath}).ToList();
158             //and check each one
159             var missingStates= (from path in statePaths
160                                 where !File.Exists(path.FilePath) && !Directory.Exists(path.FilePath)
161                                select path.Id).ToList();
162             //Finally, retrieve the states that correspond to the deleted files            
163             var deletedFiles = from state in fileStates 
164                         where missingStates.Contains(state.Id)
165                         select new { File = default(FileInfo), State = state };
166
167             var pairs = currentFiles.Union(deletedFiles);
168
169             foreach(var pair in pairs)
170             {
171                 var fileState = pair.State;
172                 var file = pair.File;
173                 if (fileState == null)
174                 {
175                     //This is a new file
176                     var fullPath = pair.File.FullName;
177                     var createState = FileState.CreateForAsync(fullPath, BlockSize, BlockHash);
178                     createState.ContinueWith(state => _persistenceAgent.Post(state.Result.Create));
179                 }                
180                 else if (file == null)
181                 {
182                     //This file was deleted while we were down. We should mark it as deleted
183                     //We have to go through UpdateStatus here because the state object we are using
184                     //was created by a different ORM session.
185                     _persistenceAgent.Post(()=> UpdateStatusDirect(fileState.Id, FileStatus.Deleted));                    
186                 }
187                 else
188                 {
189                     //This file has a matching state. Need to check for possible changes
190                     var hashString = file.CalculateHash(BlockSize,BlockHash);
191                     //If the hashes don't match the file was changed
192                     if (fileState.Checksum != hashString)
193                     {
194                         _persistenceAgent.Post(() => UpdateStatusDirect(fileState.Id, FileStatus.Modified));
195                     }                    
196                 }
197             };            
198          
199         }
200
201         private int UpdateStatusDirect(Guid id, FileStatus status)
202         {
203             using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
204             {
205
206                 try
207                 {
208                     
209                     using (var connection = GetConnection())
210                     using (
211                         var command = new SQLiteCommand("update FileState set FileStatus= :fileStatus where Id = :id  ",
212                                                         connection))
213                     {                                                
214                         command.Parameters.AddWithValue("fileStatus", status);
215
216                         command.Parameters.AddWithValue("id", id);
217                         
218                         var affected = command.ExecuteNonQuery();
219                         
220                         return affected;
221                     }
222
223                 }
224                 catch (Exception exc)
225                 {
226                     Log.Error(exc.ToString());
227                     throw;
228                 }
229             }
230         }
231         
232         private int UpdateStatusDirect(string path, FileStatus status)
233         {
234             using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
235             {
236
237                 try
238                 {
239
240                     
241                     using (var connection = GetConnection())
242                     using (
243                         var command =
244                             new SQLiteCommand("update FileState set FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE",
245                                               connection))
246                     {
247
248
249                         command.Parameters.AddWithValue("fileStatus", status);
250
251                         command.Parameters.AddWithValue("path", path);
252                         
253                         var affected = command.ExecuteNonQuery();
254                         return affected;
255                     }
256                 }
257                 catch (Exception exc)
258                 {
259                     Log.Error(exc.ToString());
260                     throw;
261                 }
262             }
263         }
264
265         private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
266         {
267             using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
268             {
269
270                 try
271                 {
272
273                     
274                     using (var connection = GetConnection())
275                     using (
276                         var command =
277                             new SQLiteCommand(
278                                 "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE ",
279                                 connection))
280                     {
281
282                         command.Parameters.AddWithValue("path", absolutePath);
283                         command.Parameters.AddWithValue("fileStatus", fileStatus);
284                         command.Parameters.AddWithValue("overlayStatus", overlayStatus);
285                         
286                         var affected = command.ExecuteNonQuery();
287                         return affected;
288                     }
289                 }
290                 catch (Exception exc)
291                 {
292                     Log.Error(exc.ToString());
293                     throw;
294                 }
295             }
296         }
297         
298
299
300         public string BlockHash { get; set; }
301
302         public int BlockSize { get; set; }
303         public void ChangeRoots(string oldPath, string newPath)
304         {
305             if (String.IsNullOrWhiteSpace(oldPath))
306                 throw new ArgumentNullException("oldPath");
307             if (!Path.IsPathRooted(oldPath))
308                 throw new ArgumentException("oldPath must be an absolute path", "oldPath");
309             if (string.IsNullOrWhiteSpace(newPath))
310                 throw new ArgumentNullException("newPath");
311             if (!Path.IsPathRooted(newPath))
312                 throw new ArgumentException("newPath must be an absolute path", "newPath");
313             Contract.EndContractBlock();
314
315             FileState.ChangeRootPath(oldPath,newPath);
316
317         }
318
319         private PithosStatus _pithosStatus=PithosStatus.InSynch;       
320
321         public void SetPithosStatus(PithosStatus status)
322         {
323             _pithosStatus = status;
324         }
325
326         public PithosStatus GetPithosStatus()
327         {
328             return _pithosStatus;
329         }
330
331
332         private readonly string _pithosDataPath;
333
334
335         public FileState GetStateByFilePath(string path)
336         {
337             if (String.IsNullOrWhiteSpace(path))
338                 throw new ArgumentNullException("path");
339             if (!Path.IsPathRooted(path))
340                 throw new ArgumentException("The path must be rooted", "path");
341             Contract.EndContractBlock();
342
343             try
344             {
345                 
346                 using (var connection = GetConnection())
347                 using (var command = new SQLiteCommand("select Id, FilePath, OverlayStatus,FileStatus ,Checksum   ,Version    ,VersionTimeStamp,IsShared   ,SharedBy   ,ShareWrite  from FileState where FilePath=:path COLLATE NOCASE", connection))
348                 {
349                     
350                     command.Parameters.AddWithValue("path", path);
351                     
352                     using (var reader = command.ExecuteReader())
353                     {
354                         if (reader.Read())
355                         {
356                             //var values = new object[reader.FieldCount];
357                             //reader.GetValues(values);
358                             var state = new FileState
359                                             {
360                                                 Id = reader.GetGuid(0),
361                                                 FilePath = reader.IsDBNull(1)?"":reader.GetString(1),
362                                                 OverlayStatus =reader.IsDBNull(2)?FileOverlayStatus.Unversioned: (FileOverlayStatus) reader.GetInt64(2),
363                                                 FileStatus = reader.IsDBNull(3)?FileStatus.Missing:(FileStatus) reader.GetInt64(3),
364                                                 Checksum = reader.IsDBNull(4)?"":reader.GetString(4),
365                                                 Version = reader.IsDBNull(5)?default(long):reader.GetInt64(5),
366                                                 VersionTimeStamp = reader.IsDBNull(6)?default(DateTime):reader.GetDateTime(6),
367                                                 IsShared = !reader.IsDBNull(7) && reader.GetBoolean(7),
368                                                 SharedBy = reader.IsDBNull(8)?"":reader.GetString(8),
369                                                 ShareWrite = !reader.IsDBNull(9) && reader.GetBoolean(9)
370                                             };
371 /*
372                             var state = new FileState
373                                             {
374                                                 Id = (Guid) values[0],
375                                                 FilePath = (string) values[1],
376                                                 OverlayStatus = (FileOverlayStatus) (long)values[2],
377                                                 FileStatus = (FileStatus) (long)values[3],
378                                                 Checksum = (string) values[4],
379                                                 Version = (long?) values[5],
380                                                 VersionTimeStamp = (DateTime?) values[6],
381                                                 IsShared = (long)values[7] == 1,
382                                                 SharedBy = (string) values[8],
383                                                 ShareWrite = (long)values[9] == 1
384                                             };
385 */
386                             return state;
387                         }
388                         else
389                         {
390                             return null;
391                         }
392
393                     }                    
394                 }
395             }
396             catch (Exception exc)
397             {
398                 Log.ErrorFormat(exc.ToString());
399                 throw;
400             }            
401         }
402
403         public FileOverlayStatus GetFileOverlayStatus(string path)
404         {
405             if (String.IsNullOrWhiteSpace(path))
406                 throw new ArgumentNullException("path");
407             if (!Path.IsPathRooted(path))
408                 throw new ArgumentException("The path must be rooted", "path");
409             Contract.EndContractBlock();
410
411             try
412             {
413                 
414                 using (var connection = GetConnection())
415                 using (var command = new SQLiteCommand("select OverlayStatus from FileState where FilePath=:path  COLLATE NOCASE", connection))
416                 {
417                     
418                     command.Parameters.AddWithValue("path", path);
419                     
420                     var s = command.ExecuteScalar();
421                     return (FileOverlayStatus) Convert.ToInt32(s);
422                 }
423             }
424             catch (Exception exc)
425             {
426                 Log.ErrorFormat(exc.ToString());
427                 return FileOverlayStatus.Unversioned;
428             }
429         }
430
431         private string GetConnectionString()
432         {
433             var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N;Pooling=True", _pithosDataPath);
434             return connectionString;
435         }
436
437         private SQLiteConnection GetConnection()
438         {
439             var connectionString = GetConnectionString();
440             var connection = new SQLiteConnection(connectionString);
441             connection.Open();
442             using(var cmd =connection.CreateCommand())
443             {
444                 cmd.CommandText = "PRAGMA journal_mode=WAL";
445                 cmd.ExecuteNonQuery();
446             }
447             return connection;
448         }
449
450         public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus)
451         {
452             if (String.IsNullOrWhiteSpace(path))
453                 throw new ArgumentNullException("path");
454             if (!Path.IsPathRooted(path))
455                 throw new ArgumentException("The path must be rooted","path");
456             Contract.EndContractBlock();
457
458             _persistenceAgent.Post(() => FileState.StoreOverlayStatus(path,overlayStatus));
459         }
460
461        /* public void RenameFileOverlayStatus(string oldPath, string newPath)
462         {
463             if (String.IsNullOrWhiteSpace(oldPath))
464                 throw new ArgumentNullException("oldPath");
465             if (!Path.IsPathRooted(oldPath))
466                 throw new ArgumentException("The oldPath must be rooted", "oldPath");
467             if (String.IsNullOrWhiteSpace(newPath))
468                 throw new ArgumentNullException("newPath");
469             if (!Path.IsPathRooted(newPath))
470                 throw new ArgumentException("The newPath must be rooted", "newPath");
471             Contract.EndContractBlock();
472
473             _persistenceAgent.Post(() =>FileState.RenameState(oldPath, newPath));
474         }*/
475
476         public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus)
477         {
478             if (String.IsNullOrWhiteSpace(path))
479                 throw new ArgumentNullException("path");
480             if (!Path.IsPathRooted(path))
481                 throw new ArgumentException("The path must be rooted", "path");
482             Contract.EndContractBlock();
483
484             Debug.Assert(!path.Contains(FolderConstants.CacheFolder));
485             Debug.Assert(!path.EndsWith(".ignore"));
486
487             _persistenceAgent.Post(() => UpdateStatusDirect(path, fileStatus, overlayStatus));
488         }
489
490 /*
491         public void StoreInfo(string path,ObjectInfo objectInfo)
492         {
493             if (String.IsNullOrWhiteSpace(path))
494                 throw new ArgumentNullException("path");
495             if (!Path.IsPathRooted(path))
496                 throw new ArgumentException("The path must be rooted", "path");            
497             if (objectInfo == null)
498                 throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");
499             Contract.EndContractBlock();
500
501             _persistenceAgent.Post(() =>
502             {
503                 var filePath = path.ToLower();
504                 //Load the existing files state and set its properties in one session            
505                 using (new SessionScope())
506                 {
507                     //Forgetting to use a sessionscope results in two sessions being created, one by 
508                     //FirstOrDefault and one by Save()
509                     var state =FileState.FindByFilePath(filePath);
510                     
511                     //Create a new empty state object if this is a new file
512                     state = state ?? new FileState();
513
514                     state.FilePath = filePath;
515                     state.Checksum = objectInfo.Hash;
516                     state.Version = objectInfo.Version;
517                     state.VersionTimeStamp = objectInfo.VersionTimestamp;
518
519                     state.FileStatus = FileStatus.Unchanged;
520                     state.OverlayStatus = FileOverlayStatus.Normal;
521                     
522                   
523                     //Do the save
524                     state.Save();
525                 }
526             });
527
528         }
529 */
530         
531         public void StoreInfo(string path, ObjectInfo objectInfo)
532         {
533             if (String.IsNullOrWhiteSpace(path))
534                 throw new ArgumentNullException("path");
535             if (!Path.IsPathRooted(path))
536                 throw new ArgumentException("The path must be rooted", "path");
537             if (objectInfo == null)
538                 throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");
539             Contract.EndContractBlock();
540
541             _persistenceAgent.Post(() => StoreInfoDirect(path, objectInfo));
542
543         }
544
545         private void StoreInfoDirect(string path, ObjectInfo objectInfo)
546         {
547             try
548             {
549                 
550                 using (var connection = GetConnection())
551                 using (var command = new SQLiteCommand(connection))
552                 {
553                     if (StateExists(path, connection))
554                         command.CommandText =
555                             "update FileState set FileStatus= :fileStatus where FilePath = :path  COLLATE NOCASE ";
556                     else
557                     {
558                         command.CommandText =
559                             "INSERT INTO FileState (Id,FilePath,Checksum,Version,VersionTimeStamp,FileStatus,OverlayStatus) VALUES (:id,:path,:checksum,:version,:versionTimeStamp,:fileStatus,:overlayStatus)";
560                         command.Parameters.AddWithValue("id", Guid.NewGuid());
561                     }
562
563                     command.Parameters.AddWithValue("path", path);
564                     command.Parameters.AddWithValue("checksum", objectInfo.Hash);
565                     command.Parameters.AddWithValue("version", objectInfo.Version);
566                     command.Parameters.AddWithValue("versionTimeStamp",
567                                                     objectInfo.VersionTimestamp);
568                     command.Parameters.AddWithValue("fileStatus", FileStatus.Unchanged);
569                     command.Parameters.AddWithValue("overlayStatus",
570                                                     FileOverlayStatus.Normal);
571
572                     var affected = command.ExecuteNonQuery();
573                     return;
574                 }
575             }
576             catch (Exception exc)
577             {
578                 Log.Error(exc.ToString());
579                 throw;
580             }
581         }
582
583         private bool StateExists(string filePath,SQLiteConnection connection)
584         {
585             using (var command = new SQLiteCommand("Select count(*) from FileState where FilePath=:path  COLLATE NOCASE", connection))
586             {
587                 command.Parameters.AddWithValue("path", filePath);
588                 var result = command.ExecuteScalar();
589                 return ((long)result >= 1);
590             }
591
592         }
593
594         public void SetFileStatus(string path, FileStatus status)
595         {
596             if (String.IsNullOrWhiteSpace(path))
597                 throw new ArgumentNullException("path");
598             if (!Path.IsPathRooted(path))
599                 throw new ArgumentException("The path must be rooted", "path");
600             Contract.EndContractBlock();
601             
602             _persistenceAgent.Post(() => UpdateStatusDirect(path, status));
603         }
604
605         public FileStatus GetFileStatus(string path)
606         {
607             if (String.IsNullOrWhiteSpace(path))
608                 throw new ArgumentNullException("path");
609             if (!Path.IsPathRooted(path))
610                 throw new ArgumentException("The path must be rooted", "path");
611             Contract.EndContractBlock();
612
613             
614             using (var connection = GetConnection())
615             {
616                 var command = new SQLiteCommand("select FileStatus from FileState where FilePath=:path  COLLATE NOCASE", connection);
617                 command.Parameters.AddWithValue("path", path);
618                 
619                 var statusValue = command.ExecuteScalar();
620                 if (statusValue==null)
621                     return FileStatus.Missing;
622                 return (FileStatus)Convert.ToInt32(statusValue);
623             }
624         }
625
626         /// <summary>
627         /// Deletes the status of the specified file
628         /// </summary>
629         /// <param name="path"></param>
630         public void ClearFileStatus(string path)
631         {
632             if (String.IsNullOrWhiteSpace(path))
633                 throw new ArgumentNullException("path");
634             if (!Path.IsPathRooted(path))
635                 throw new ArgumentException("The path must be rooted", "path");
636             Contract.EndContractBlock();
637
638             _persistenceAgent.Post(() => DeleteDirect(path));   
639         }
640
641         /// <summary>
642         /// Deletes the status of the specified folder and all its contents
643         /// </summary>
644         /// <param name="path"></param>
645         public void ClearFolderStatus(string path)
646         {
647             if (String.IsNullOrWhiteSpace(path))
648                 throw new ArgumentNullException("path");
649             if (!Path.IsPathRooted(path))
650                 throw new ArgumentException("The path must be rooted", "path");
651             Contract.EndContractBlock();
652
653             _persistenceAgent.Post(() => DeleteFolderDirect(path));   
654         }
655
656         public IEnumerable<FileState> GetChildren(FileState fileState)
657         {
658             if (fileState == null)
659                 throw new ArgumentNullException("fileState");
660             Contract.EndContractBlock();
661
662             var children = from state in FileState.Queryable
663                            where state.FilePath.StartsWith(fileState.FilePath + "\\")
664                            select state;
665             return children;
666         }
667
668         private int DeleteDirect(string filePath)
669         {
670             using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))
671             {
672
673                 try
674                 {
675
676                     
677                     using (var connection = GetConnection())
678                     {
679                         var command = new SQLiteCommand("delete from FileState where FilePath = :path  COLLATE NOCASE",
680                                                         connection);
681
682                         command.Parameters.AddWithValue("path", filePath);
683                         
684                         var affected = command.ExecuteNonQuery();
685                         return affected;
686                     }
687                 }
688                 catch (Exception exc)
689                 {
690                     Log.Error(exc.ToString());
691                     throw;
692                 }
693             }
694         }
695
696         private int DeleteFolderDirect(string filePath)
697         {
698             using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))
699             {
700
701                 try
702                 {
703
704                     
705                     using (var connection = GetConnection())
706                     {
707                         var command = new SQLiteCommand("delete from FileState where FilePath = :path or FilePath like :path + '/%'  COLLATE NOCASE",
708                                                         connection);
709
710                         command.Parameters.AddWithValue("path", filePath);
711                         
712                         var affected = command.ExecuteNonQuery();
713                         return affected;
714                     }
715                 }
716                 catch (Exception exc)
717                 {
718                     Log.Error(exc.ToString());
719                     throw;
720                 }
721             }
722         }
723
724         public void UpdateFileChecksum(string path, string checksum)
725         {
726             if (String.IsNullOrWhiteSpace(path))
727                 throw new ArgumentNullException("path");
728             if (!Path.IsPathRooted(path))
729                 throw new ArgumentException("The path must be rooted", "path");            
730             Contract.EndContractBlock();
731
732             _persistenceAgent.Post(() => FileState.UpdateChecksum(path, checksum));
733         }
734
735     }
736
737    
738 }