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