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