Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / StatusAgent.cs @ 81c5c310

History | View | Annotate | Download (33.8 kB)

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
}