299b41f6b1b6bdca975fbbaf11a035997dd7c0a7
[pithos-ms-client] / trunk%2FPithos.Core%2FAgents%2FStatusAgent.cs
1 #region
2 /* -----------------------------------------------------------------------
3  * <copyright file="StatusAgent.cs" company="GRNet">
4  * 
5  * Copyright 2011-2012 GRNET S.A. All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or
8  * without modification, are permitted provided that the following
9  * conditions are met:
10  *
11  *   1. Redistributions of source code must retain the above
12  *      copyright notice, this list of conditions and the following
13  *      disclaimer.
14  *
15  *   2. Redistributions in binary form must reproduce the above
16  *      copyright notice, this list of conditions and the following
17  *      disclaimer in the documentation and/or other materials
18  *      provided with the distribution.
19  *
20  *
21  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
22  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
25  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *
34  * The views and conclusions contained in the software and
35  * documentation are those of the authors and should not be
36  * interpreted as representing official policies, either expressed
37  * or implied, of GRNET S.A.
38  * </copyright>
39  * -----------------------------------------------------------------------
40  */
41 #endregion
42 using System;
43 using System.Collections.Generic;
44 using System.ComponentModel.Composition;
45 using System.Data.SQLite;
46 using System.Diagnostics;
47 using System.Diagnostics.Contracts;
48 using System.IO;
49 using System.Linq;
50 using System.Reflection;
51 using System.Security.Cryptography;
52 using System.Text;
53 using System.Threading;
54 using System.Threading.Tasks;
55 using Castle.ActiveRecord;
56 using Castle.ActiveRecord.Framework;
57 using Castle.ActiveRecord.Framework.Config;
58 using NHibernate.ByteCode.Castle;
59 using NHibernate.Cfg;
60 using NHibernate.Cfg.Loquacious;
61 using NHibernate.Dialect;
62 using Pithos.Interfaces;
63 using Pithos.Network;
64 using log4net;
65 using Environment = System.Environment;
66
67 namespace Pithos.Core.Agents
68 {
69     [Export(typeof(IStatusChecker)),Export(typeof(IStatusKeeper))]
70     public class StatusAgent:IStatusChecker,IStatusKeeper
71     {
72         private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
73
74         [System.ComponentModel.Composition.Import]
75         public IPithosSettings Settings { get; set; }
76
77         private Agent<Action> _persistenceAgent;
78
79
80
81         public StatusAgent()
82         {            
83             var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
84
85             _pithosDataPath = Path.Combine(appDataPath , "GRNET\\PITHOS");
86             if (!Directory.Exists(_pithosDataPath))
87                 Directory.CreateDirectory(_pithosDataPath);
88
89             var dbPath = Path.Combine(_pithosDataPath, "pithos.db");
90
91             MigrateOldDb(dbPath, appDataPath);
92
93
94             var source = GetConfiguration(_pithosDataPath);
95             ActiveRecordStarter.Initialize(source,typeof(FileState),typeof(FileTag));
96             
97             ActiveRecordStarter.UpdateSchema();
98
99
100             if (!File.Exists(dbPath))
101                 ActiveRecordStarter.CreateSchema();
102
103             CreateTrigger();
104             
105         }
106
107
108         private static void MigrateOldDb(string dbPath, string appDataPath)
109         {
110             Contract.Requires(!String.IsNullOrWhiteSpace(dbPath));
111             Contract.Requires(!String.IsNullOrWhiteSpace(appDataPath));
112
113
114             var oldDbPath = Path.Combine(appDataPath, "Pithos", "pithos.db");
115             var oldDbInfo = new FileInfo(oldDbPath);
116             if (oldDbInfo.Exists && !File.Exists(dbPath))
117             {
118                 Log.InfoFormat("Moving database from {0} to {1}",oldDbInfo.FullName,dbPath);
119                 var oldDirectory = oldDbInfo.Directory;
120                 oldDbInfo.MoveTo(dbPath);
121                 
122                 if (Log.IsDebugEnabled)
123                     Log.DebugFormat("Deleting {0}",oldDirectory.FullName);
124                 
125                 oldDirectory.Delete(true);
126             }
127         }
128
129         private void CreateTrigger()
130         {
131             using (var connection = GetConnection())
132             using (var triggerCommand = connection.CreateCommand())
133             {
134                 var cmdText = new StringBuilder()
135                     .AppendLine("CREATE TRIGGER IF NOT EXISTS update_last_modified UPDATE ON FileState FOR EACH ROW")
136                     .AppendLine("BEGIN")
137                     .AppendLine("UPDATE FileState SET Modified=datetime('now')  WHERE Id=old.Id;")
138                     .AppendLine("END;")
139                     .AppendLine("CREATE TRIGGER IF NOT EXISTS insert_last_modified INSERT ON FileState FOR EACH ROW")
140                     .AppendLine("BEGIN")
141                     .AppendLine("UPDATE FileState SET Modified=datetime('now')  WHERE Id=new.Id;")
142                     .AppendLine("END;")
143                     .ToString();
144                 triggerCommand.CommandText = cmdText;                
145                 triggerCommand.ExecuteNonQuery();
146             }
147         }
148
149
150         private static InPlaceConfigurationSource GetConfiguration(string pithosDbPath)
151         {
152             if (String.IsNullOrWhiteSpace(pithosDbPath))
153                 throw new ArgumentNullException("pithosDbPath");
154             if (!Path.IsPathRooted(pithosDbPath))
155                 throw new ArgumentException("path must be a rooted path", "pithosDbPath");
156             Contract.EndContractBlock();
157
158             var properties = new Dictionary<string, string>
159                                  {
160                                      {"connection.driver_class", "NHibernate.Driver.SQLite20Driver"},
161                                      {"dialect", "NHibernate.Dialect.SQLiteDialect"},
162                                      {"connection.provider", "NHibernate.Connection.DriverConnectionProvider"},
163                                      {
164                                          "proxyfactory.factory_class",
165                                          "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"
166                                          },
167                                  };
168
169             var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N", pithosDbPath);
170             properties.Add("connection.connection_string", connectionString);
171
172             var source = new InPlaceConfigurationSource();                        
173             source.Add(typeof (ActiveRecordBase), properties);
174             source.SetDebugFlag(false);            
175             return source;
176         }
177
178         public void StartProcessing(CancellationToken token)
179         {
180             _persistenceAgent = Agent<Action>.Start(queue =>
181             {
182                 Action loop = null;
183                 loop = () =>
184                 {
185                     var job = queue.Receive();
186                     job.ContinueWith(t =>
187                     {
188                         var action = job.Result;
189                         try
190                         {
191                             action();
192                         }
193                         catch (SQLiteException ex)
194                         {
195                             Log.ErrorFormat("[ERROR] SQL \n{0}", ex);
196                         }
197                         catch (Exception ex)
198                         {
199                             Log.ErrorFormat("[ERROR] STATE \n{0}", ex);
200                         }
201                         queue.NotifyComplete(action);
202 // ReSharper disable AccessToModifiedClosure
203                         queue.DoAsync(loop);
204 // ReSharper restore AccessToModifiedClosure
205                     });
206                 };
207                 loop();
208             });
209             
210         }
211
212        
213
214         public void Stop()
215         {
216             _persistenceAgent.Stop();            
217         }
218                
219
220         public void ProcessExistingFiles(IEnumerable<FileInfo> existingFiles)
221         {
222             if(existingFiles  ==null)
223                 throw new ArgumentNullException("existingFiles");
224             Contract.EndContractBlock();
225             
226             //Find new or matching files with a left join to the stored states
227             var fileStates = FileState.Queryable;
228             var currentFiles=from file in existingFiles
229                       join state in fileStates on file.FullName.ToLower() equals state.FilePath.ToLower() into gs
230                       from substate in gs.DefaultIfEmpty()
231                                select new {File = file, State = substate};
232
233             //To get the deleted files we must get the states that have no corresponding
234             //files. 
235             //We can't use the File.Exists method inside a query, so we get all file paths from the states
236             var statePaths = (from state in fileStates
237                              select new {state.Id, state.FilePath}).ToList();
238             //and check each one
239             var missingStates= (from path in statePaths
240                                 where !File.Exists(path.FilePath) && !Directory.Exists(path.FilePath)
241                                select path.Id).ToList();
242             //Finally, retrieve the states that correspond to the deleted files            
243             var deletedFiles = from state in fileStates 
244                         where missingStates.Contains(state.Id)
245                         select new { File = default(FileInfo), State = state };
246
247             var pairs = currentFiles.Union(deletedFiles).ToList();
248
249             using (var shortHasher = HashAlgorithm.Create("sha1"))
250             {
251                 foreach (var pair in pairs)
252                 {
253                     var fileState = pair.State;
254                     var file = pair.File;
255                     if (fileState == null)
256                     {
257                         //This is a new file                        
258                         var createState = FileState.CreateFor(file);
259                         _persistenceAgent.Post(createState.Create);                        
260                     }
261                     else if (file == null)
262                     {
263                         //This file was deleted while we were down. We should mark it as deleted
264                         //We have to go through UpdateStatus here because the state object we are using
265                         //was created by a different ORM session.
266                         _persistenceAgent.Post(() => UpdateStatusDirect(fileState.Id, FileStatus.Deleted));
267                     }
268                     else
269                     {
270                         //This file has a matching state. Need to check for possible changes
271                         //To check for changes, we use the cheap (in CPU terms) SHA1 algorithm
272                         //on the entire file.
273                         
274                         var hashString = file.ComputeShortHash(shortHasher);                        
275                         //TODO: Need a way to attach the hashes to the filestate so we don't
276                         //recalculate them each time a call to calculate has is made
277                         //We can either store them to the filestate or add them to a 
278                         //dictionary
279
280                         //If the hashes don't match the file was changed
281                         if (fileState.ShortHash != hashString)
282                         {
283                             _persistenceAgent.Post(() => UpdateStatusDirect(fileState.Id, FileStatus.Modified));
284                         }
285                     }
286                 }
287             }
288                         
289          
290         }
291         
292
293
294         private int UpdateStatusDirect(Guid id, FileStatus status)
295         {
296             using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
297             {
298
299                 try
300                 {
301                     
302                     using (var connection = GetConnection())
303                     using (
304                         var command = new SQLiteCommand("update FileState set FileStatus= :fileStatus where Id = :id  ",
305                                                         connection))
306                     {                                                
307                         command.Parameters.AddWithValue("fileStatus", status);
308
309                         command.Parameters.AddWithValue("id", id);
310                         
311                         var affected = command.ExecuteNonQuery();
312                         
313                         return affected;
314                     }
315
316                 }
317                 catch (Exception exc)
318                 {
319                     Log.Error(exc.ToString());
320                     throw;
321                 }
322             }
323         }
324         
325         private int UpdateStatusDirect(string path, FileStatus status)
326         {
327             using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
328             {
329
330                 try
331                 {
332
333                     
334                     using (var connection = GetConnection())
335                     using (
336                         var command =
337                             new SQLiteCommand("update FileState set FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE",
338                                               connection))
339                     {
340
341
342                         command.Parameters.AddWithValue("fileStatus", status);
343
344                         command.Parameters.AddWithValue("path", path);
345                         
346                         var affected = command.ExecuteNonQuery();
347                         return affected;
348                     }
349                 }
350                 catch (Exception exc)
351                 {
352                     Log.Error(exc.ToString());
353                     throw;
354                 }
355             }
356         }
357
358         private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
359         {
360             using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
361             {
362
363                 try
364                 {
365
366                     
367                     using (var connection = GetConnection())
368                     using (
369                         var command =
370                             new SQLiteCommand(
371                                 "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE ",
372                                 connection))
373                     {
374
375                         command.Parameters.AddWithValue("path", absolutePath);
376                         command.Parameters.AddWithValue("fileStatus", fileStatus);
377                         command.Parameters.AddWithValue("overlayStatus", overlayStatus);
378                         
379                         var affected = command.ExecuteNonQuery();
380                         return affected;
381                     }
382                 }
383                 catch (Exception exc)
384                 {
385                     Log.Error(exc.ToString());
386                     throw;
387                 }
388             }
389         }
390         
391
392
393         public string BlockHash { get; set; }
394
395         public int BlockSize { get; set; }
396         public void ChangeRoots(string oldPath, string newPath)
397         {
398             if (String.IsNullOrWhiteSpace(oldPath))
399                 throw new ArgumentNullException("oldPath");
400             if (!Path.IsPathRooted(oldPath))
401                 throw new ArgumentException("oldPath must be an absolute path", "oldPath");
402             if (string.IsNullOrWhiteSpace(newPath))
403                 throw new ArgumentNullException("newPath");
404             if (!Path.IsPathRooted(newPath))
405                 throw new ArgumentException("newPath must be an absolute path", "newPath");
406             Contract.EndContractBlock();
407
408             FileState.ChangeRootPath(oldPath,newPath);
409
410         }
411
412
413
414         private readonly string _pithosDataPath;
415
416
417         public FileState GetStateByFilePath(string path)
418         {
419             if (String.IsNullOrWhiteSpace(path))
420                 throw new ArgumentNullException("path");
421             if (!Path.IsPathRooted(path))
422                 throw new ArgumentException("The path must be rooted", "path");
423             Contract.EndContractBlock();
424
425             try
426             {
427                 
428                 using (var connection = GetConnection())
429                 using (var command = new SQLiteCommand("select Id, FilePath, OverlayStatus,FileStatus ,Checksum ,ShortHash,Version    ,VersionTimeStamp,IsShared   ,SharedBy   ,ShareWrite  from FileState where FilePath=:path COLLATE NOCASE", connection))
430                 {
431                     
432                     command.Parameters.AddWithValue("path", path);
433                     
434                     using (var reader = command.ExecuteReader())
435                     {
436                         if (reader.Read())
437                         {
438                             //var values = new object[reader.FieldCount];
439                             //reader.GetValues(values);
440                             var state = new FileState
441                                             {
442                                                 Id = reader.GetGuid(0),
443                                                 FilePath = reader.IsDBNull(1)?"":reader.GetString(1),
444                                                 OverlayStatus =reader.IsDBNull(2)?FileOverlayStatus.Unversioned: (FileOverlayStatus) reader.GetInt64(2),
445                                                 FileStatus = reader.IsDBNull(3)?FileStatus.Missing:(FileStatus) reader.GetInt64(3),
446                                                 Checksum = reader.IsDBNull(4)?"":reader.GetString(4),
447                                                 ShortHash= reader.IsDBNull(5)?"":reader.GetString(5),
448                                                 Version = reader.IsDBNull(6)?default(long):reader.GetInt64(6),
449                                                 VersionTimeStamp = reader.IsDBNull(7)?default(DateTime):reader.GetDateTime(7),
450                                                 IsShared = !reader.IsDBNull(8) && reader.GetBoolean(8),
451                                                 SharedBy = reader.IsDBNull(9)?"":reader.GetString(9),
452                                                 ShareWrite = !reader.IsDBNull(10) && reader.GetBoolean(10)
453                                             };
454 /*
455                             var state = new FileState
456                                             {
457                                                 Id = (Guid) values[0],
458                                                 FilePath = (string) values[1],
459                                                 OverlayStatus = (FileOverlayStatus) (long)values[2],
460                                                 FileStatus = (FileStatus) (long)values[3],
461                                                 Checksum = (string) values[4],
462                                                 Version = (long?) values[5],
463                                                 VersionTimeStamp = (DateTime?) values[6],
464                                                 IsShared = (long)values[7] == 1,
465                                                 SharedBy = (string) values[8],
466                                                 ShareWrite = (long)values[9] == 1
467                                             };
468 */
469                             return state;
470                         }
471                         else
472                         {
473                             return null;
474                         }
475
476                     }                    
477                 }
478             }
479             catch (Exception exc)
480             {
481                 Log.ErrorFormat(exc.ToString());
482                 throw;
483             }            
484         }
485
486         public FileOverlayStatus GetFileOverlayStatus(string path)
487         {
488             if (String.IsNullOrWhiteSpace(path))
489                 throw new ArgumentNullException("path");
490             if (!Path.IsPathRooted(path))
491                 throw new ArgumentException("The path must be rooted", "path");
492             Contract.EndContractBlock();
493
494             try
495             {
496                 
497                 using (var connection = GetConnection())
498                 using (var command = new SQLiteCommand("select OverlayStatus from FileState where FilePath=:path  COLLATE NOCASE", connection))
499                 {
500                     
501                     command.Parameters.AddWithValue("path", path);
502                     
503                     var s = command.ExecuteScalar();
504                     return (FileOverlayStatus) Convert.ToInt32(s);
505                 }
506             }
507             catch (Exception exc)
508             {
509                 Log.ErrorFormat(exc.ToString());
510                 return FileOverlayStatus.Unversioned;
511             }
512         }
513
514         private string GetConnectionString()
515         {
516             var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N;Pooling=True", _pithosDataPath);
517             return connectionString;
518         }
519
520         private SQLiteConnection GetConnection()
521         {
522             var connectionString = GetConnectionString();
523             var connection = new SQLiteConnection(connectionString);
524             connection.Open();
525             using(var cmd =connection.CreateCommand())
526             {
527                 cmd.CommandText = "PRAGMA journal_mode=WAL";
528                 cmd.ExecuteNonQuery();
529             }
530             return connection;
531         }
532
533        /* public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus)
534         {
535             if (String.IsNullOrWhiteSpace(path))
536                 throw new ArgumentNullException("path");
537             if (!Path.IsPathRooted(path))
538                 throw new ArgumentException("The path must be rooted","path");
539             Contract.EndContractBlock();
540
541             _persistenceAgent.Post(() => FileState.StoreOverlayStatus(path,overlayStatus));
542         }*/
543
544         public Task SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus, string shortHash = null)
545         {
546             if (String.IsNullOrWhiteSpace(path))
547                 throw new ArgumentNullException("path");
548             if (!Path.IsPathRooted(path))
549                 throw new ArgumentException("The path must be rooted","path");
550             Contract.EndContractBlock();
551
552             return _persistenceAgent.PostAndAwait(() => FileState.StoreOverlayStatus(path,overlayStatus,shortHash));
553         }
554
555        /* public void RenameFileOverlayStatus(string oldPath, string newPath)
556         {
557             if (String.IsNullOrWhiteSpace(oldPath))
558                 throw new ArgumentNullException("oldPath");
559             if (!Path.IsPathRooted(oldPath))
560                 throw new ArgumentException("The oldPath must be rooted", "oldPath");
561             if (String.IsNullOrWhiteSpace(newPath))
562                 throw new ArgumentNullException("newPath");
563             if (!Path.IsPathRooted(newPath))
564                 throw new ArgumentException("The newPath must be rooted", "newPath");
565             Contract.EndContractBlock();
566
567             _persistenceAgent.Post(() =>FileState.RenameState(oldPath, newPath));
568         }*/
569
570         public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus)
571         {
572             if (String.IsNullOrWhiteSpace(path))
573                 throw new ArgumentNullException("path");
574             if (!Path.IsPathRooted(path))
575                 throw new ArgumentException("The path must be rooted", "path");
576             Contract.EndContractBlock();
577
578             Debug.Assert(!path.Contains(FolderConstants.CacheFolder));
579             Debug.Assert(!path.EndsWith(".ignore"));
580
581             _persistenceAgent.Post(() => UpdateStatusDirect(path, fileStatus, overlayStatus));
582         }
583
584 /*
585         public void StoreInfo(string path,ObjectInfo objectInfo)
586         {
587             if (String.IsNullOrWhiteSpace(path))
588                 throw new ArgumentNullException("path");
589             if (!Path.IsPathRooted(path))
590                 throw new ArgumentException("The path must be rooted", "path");            
591             if (objectInfo == null)
592                 throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");
593             Contract.EndContractBlock();
594
595             _persistenceAgent.Post(() =>
596             {
597                 var filePath = path.ToLower();
598                 //Load the existing files state and set its properties in one session            
599                 using (new SessionScope())
600                 {
601                     //Forgetting to use a sessionscope results in two sessions being created, one by 
602                     //FirstOrDefault and one by Save()
603                     var state =FileState.FindByFilePath(filePath);
604                     
605                     //Create a new empty state object if this is a new file
606                     state = state ?? new FileState();
607
608                     state.FilePath = filePath;
609                     state.Checksum = objectInfo.Hash;
610                     state.Version = objectInfo.Version;
611                     state.VersionTimeStamp = objectInfo.VersionTimestamp;
612
613                     state.FileStatus = FileStatus.Unchanged;
614                     state.OverlayStatus = FileOverlayStatus.Normal;
615                     
616                   
617                     //Do the save
618                     state.Save();
619                 }
620             });
621
622         }
623 */
624         
625         public void StoreInfo(string path, ObjectInfo objectInfo)
626         {
627             if (String.IsNullOrWhiteSpace(path))
628                 throw new ArgumentNullException("path");
629             if (!Path.IsPathRooted(path))
630                 throw new ArgumentException("The path must be rooted", "path");
631             if (objectInfo == null)
632                 throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");
633             Contract.EndContractBlock();
634
635             _persistenceAgent.Post(() => StoreInfoDirect(path, objectInfo));
636
637         }
638
639         private void StoreInfoDirect(string path, ObjectInfo objectInfo)
640         {
641             try
642             {
643                 
644                 using (var connection = GetConnection())
645                 using (var command = new SQLiteCommand(connection))
646                 {
647                     if (StateExists(path, connection))
648                         command.CommandText =
649                             "update FileState set FileStatus= :fileStatus where FilePath = :path  COLLATE NOCASE ";
650                     else
651                     {
652                         command.CommandText =
653                             "INSERT INTO FileState (Id,FilePath,Checksum,Version,VersionTimeStamp,ShortHash,FileStatus,OverlayStatus) VALUES (:id,:path,:checksum,:version,:versionTimeStamp,:shortHash,:fileStatus,:overlayStatus)";
654                         command.Parameters.AddWithValue("id", Guid.NewGuid());
655                     }
656
657                     command.Parameters.AddWithValue("path", path);
658                     command.Parameters.AddWithValue("checksum", objectInfo.Hash);
659                     command.Parameters.AddWithValue("shortHash", "");
660                     command.Parameters.AddWithValue("version", objectInfo.Version);
661                     command.Parameters.AddWithValue("versionTimeStamp",
662                                                     objectInfo.VersionTimestamp);
663                     command.Parameters.AddWithValue("fileStatus", FileStatus.Unchanged);
664                     command.Parameters.AddWithValue("overlayStatus",
665                                                     FileOverlayStatus.Normal);
666
667                     var affected = command.ExecuteNonQuery();
668                     return;
669                 }
670             }
671             catch (Exception exc)
672             {
673                 Log.Error(exc.ToString());
674                 throw;
675             }
676         }
677
678         private bool StateExists(string filePath,SQLiteConnection connection)
679         {
680             using (var command = new SQLiteCommand("Select count(*) from FileState where FilePath=:path  COLLATE NOCASE", connection))
681             {
682                 command.Parameters.AddWithValue("path", filePath);
683                 var result = command.ExecuteScalar();
684                 return ((long)result >= 1);
685             }
686
687         }
688
689         public void SetFileStatus(string path, FileStatus status)
690         {
691             if (String.IsNullOrWhiteSpace(path))
692                 throw new ArgumentNullException("path");
693             if (!Path.IsPathRooted(path))
694                 throw new ArgumentException("The path must be rooted", "path");
695             Contract.EndContractBlock();
696             
697             _persistenceAgent.Post(() => UpdateStatusDirect(path, status));
698         }
699
700         public FileStatus GetFileStatus(string path)
701         {
702             if (String.IsNullOrWhiteSpace(path))
703                 throw new ArgumentNullException("path");
704             if (!Path.IsPathRooted(path))
705                 throw new ArgumentException("The path must be rooted", "path");
706             Contract.EndContractBlock();
707
708             
709             using (var connection = GetConnection())
710             {
711                 var command = new SQLiteCommand("select FileStatus from FileState where FilePath=:path  COLLATE NOCASE", connection);
712                 command.Parameters.AddWithValue("path", path);
713                 
714                 var statusValue = command.ExecuteScalar();
715                 if (statusValue==null)
716                     return FileStatus.Missing;
717                 return (FileStatus)Convert.ToInt32(statusValue);
718             }
719         }
720
721         /// <summary>
722         /// Deletes the status of the specified file
723         /// </summary>
724         /// <param name="path"></param>
725         public void ClearFileStatus(string path)
726         {
727             if (String.IsNullOrWhiteSpace(path))
728                 throw new ArgumentNullException("path");
729             if (!Path.IsPathRooted(path))
730                 throw new ArgumentException("The path must be rooted", "path");
731             Contract.EndContractBlock();
732
733             _persistenceAgent.Post(() => DeleteDirect(path));   
734         }
735
736         /// <summary>
737         /// Deletes the status of the specified folder and all its contents
738         /// </summary>
739         /// <param name="path"></param>
740         public void ClearFolderStatus(string path)
741         {
742             if (String.IsNullOrWhiteSpace(path))
743                 throw new ArgumentNullException("path");
744             if (!Path.IsPathRooted(path))
745                 throw new ArgumentException("The path must be rooted", "path");
746             Contract.EndContractBlock();
747
748             _persistenceAgent.Post(() => DeleteFolderDirect(path));   
749         }
750
751         public IEnumerable<FileState> GetChildren(FileState fileState)
752         {
753             if (fileState == null)
754                 throw new ArgumentNullException("fileState");
755             Contract.EndContractBlock();
756
757             var children = from state in FileState.Queryable
758                            where state.FilePath.StartsWith(fileState.FilePath + "\\")
759                            select state;
760             return children;
761         }
762
763         public void EnsureFileState(string path)
764         {
765             var existingState = GetStateByFilePath(path);
766             if (existingState != null)
767                 return;
768             var fileInfo = FileInfoExtensions.FromPath(path);
769             using (new SessionScope())
770             {
771                 var newState = FileState.CreateFor(fileInfo);
772                 newState.FileStatus=FileStatus.Missing;
773                 _persistenceAgent.PostAndAwait(newState.CreateAndFlush).Wait();
774             }
775
776         }
777
778         private int DeleteDirect(string filePath)
779         {
780             using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))
781             {
782
783                 try
784                 {
785
786                     
787                     using (var connection = GetConnection())
788                     {
789                         var command = new SQLiteCommand("delete from FileState where FilePath = :path  COLLATE NOCASE",
790                                                         connection);
791
792                         command.Parameters.AddWithValue("path", filePath);
793                         
794                         var affected = command.ExecuteNonQuery();
795                         return affected;
796                     }
797                 }
798                 catch (Exception exc)
799                 {
800                     Log.Error(exc.ToString());
801                     throw;
802                 }
803             }
804         }
805
806         private int DeleteFolderDirect(string filePath)
807         {
808             using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))
809             {
810
811                 try
812                 {
813
814                     
815                     using (var connection = GetConnection())
816                     {
817                         var command = new SQLiteCommand(@"delete from FileState where FilePath = :path or FilePath like :path || '\%'  COLLATE NOCASE",
818                                                         connection);
819
820                         command.Parameters.AddWithValue("path", filePath);
821                         
822                         var affected = command.ExecuteNonQuery();
823                         return affected;
824                     }
825                 }
826                 catch (Exception exc)
827                 {
828                     Log.Error(exc.ToString());
829                     throw;
830                 }
831             }
832         }
833
834         public void UpdateFileChecksum(string path, string shortHash, string checksum)
835         {
836             if (String.IsNullOrWhiteSpace(path))
837                 throw new ArgumentNullException("path");
838             if (!Path.IsPathRooted(path))
839                 throw new ArgumentException("The path must be rooted", "path");            
840             Contract.EndContractBlock();
841
842             _persistenceAgent.Post(() => FileState.UpdateChecksum(path, shortHash,checksum));
843         }
844
845     }
846
847    
848 }