Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / StatusAgent.cs @ adfa4645

History | View | Annotate | Download (33.6 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
            var oldDbPath = Path.Combine(appDataPath, "Pithos", "pithos.db");
114
            var oldDbInfo = new FileInfo(oldDbPath);
115
            if (oldDbInfo.Exists && !File.Exists(dbPath))
116
            {
117
                var oldDirectory = oldDbInfo.Directory;
118
                oldDbInfo.MoveTo(dbPath);                
119
                oldDirectory.Delete(true);
120
            }
121
        }
122

    
123
        private void CreateTrigger()
124
        {
125
            using (var connection = GetConnection())
126
            using (var triggerCommand = connection.CreateCommand())
127
            {
128
                var cmdText = new StringBuilder()
129
                    .AppendLine("CREATE TRIGGER IF NOT EXISTS update_last_modified UPDATE ON FileState FOR EACH ROW")
130
                    .AppendLine("BEGIN")
131
                    .AppendLine("UPDATE FileState SET Modified=datetime('now')  WHERE Id=old.Id;")
132
                    .AppendLine("END;")
133
                    .AppendLine("CREATE TRIGGER IF NOT EXISTS insert_last_modified INSERT ON FileState FOR EACH ROW")
134
                    .AppendLine("BEGIN")
135
                    .AppendLine("UPDATE FileState SET Modified=datetime('now')  WHERE Id=new.Id;")
136
                    .AppendLine("END;")
137
                    .ToString();
138
                triggerCommand.CommandText = cmdText;                
139
                triggerCommand.ExecuteNonQuery();
140
            }
141
        }
142

    
143

    
144
        private static InPlaceConfigurationSource GetConfiguration(string pithosDbPath)
145
        {
146
            if (String.IsNullOrWhiteSpace(pithosDbPath))
147
                throw new ArgumentNullException("pithosDbPath");
148
            if (!Path.IsPathRooted(pithosDbPath))
149
                throw new ArgumentException("path must be a rooted path", "pithosDbPath");
150
            Contract.EndContractBlock();
151

    
152
            var properties = new Dictionary<string, string>
153
                                 {
154
                                     {"connection.driver_class", "NHibernate.Driver.SQLite20Driver"},
155
                                     {"dialect", "NHibernate.Dialect.SQLiteDialect"},
156
                                     {"connection.provider", "NHibernate.Connection.DriverConnectionProvider"},
157
                                     {
158
                                         "proxyfactory.factory_class",
159
                                         "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"
160
                                         },
161
                                 };
162

    
163
            var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N", pithosDbPath);
164
            properties.Add("connection.connection_string", connectionString);
165

    
166
            var source = new InPlaceConfigurationSource();                        
167
            source.Add(typeof (ActiveRecordBase), properties);
168
            source.SetDebugFlag(false);            
169
            return source;
170
        }
171

    
172
        public void StartProcessing(CancellationToken token)
173
        {
174
            _persistenceAgent = Agent<Action>.Start(queue =>
175
            {
176
                Action loop = null;
177
                loop = () =>
178
                {
179
                    var job = queue.Receive();
180
                    job.ContinueWith(t =>
181
                    {
182
                        var action = job.Result;
183
                        try
184
                        {
185
                            action();
186
                        }
187
                        catch (SQLiteException ex)
188
                        {
189
                            Log.ErrorFormat("[ERROR] SQL \n{0}", ex);
190
                        }
191
                        catch (Exception ex)
192
                        {
193
                            Log.ErrorFormat("[ERROR] STATE \n{0}", ex);
194
                        }
195
                        queue.NotifyComplete(action);
196
// ReSharper disable AccessToModifiedClosure
197
                        queue.DoAsync(loop);
198
// ReSharper restore AccessToModifiedClosure
199
                    });
200
                };
201
                loop();
202
            });
203
            
204
        }
205

    
206
       
207

    
208
        public void Stop()
209
        {
210
            _persistenceAgent.Stop();            
211
        }
212
               
213

    
214
        public void ProcessExistingFiles(IEnumerable<FileInfo> existingFiles)
215
        {
216
            if(existingFiles  ==null)
217
                throw new ArgumentNullException("existingFiles");
218
            Contract.EndContractBlock();
219
            
220
            //Find new or matching files with a left join to the stored states
221
            var fileStates = FileState.Queryable;
222
            var currentFiles=from file in existingFiles
223
                      join state in fileStates on file.FullName.ToLower() equals state.FilePath.ToLower() into gs
224
                      from substate in gs.DefaultIfEmpty()
225
                               select new {File = file, State = substate};
226

    
227
            //To get the deleted files we must get the states that have no corresponding
228
            //files. 
229
            //We can't use the File.Exists method inside a query, so we get all file paths from the states
230
            var statePaths = (from state in fileStates
231
                             select new {state.Id, state.FilePath}).ToList();
232
            //and check each one
233
            var missingStates= (from path in statePaths
234
                                where !File.Exists(path.FilePath) && !Directory.Exists(path.FilePath)
235
                               select path.Id).ToList();
236
            //Finally, retrieve the states that correspond to the deleted files            
237
            var deletedFiles = from state in fileStates 
238
                        where missingStates.Contains(state.Id)
239
                        select new { File = default(FileInfo), State = state };
240

    
241
            var pairs = currentFiles.Union(deletedFiles).ToList();
242

    
243
            using (var shortHasher = HashAlgorithm.Create("sha1"))
244
            {
245
                foreach (var pair in pairs)
246
                {
247
                    var fileState = pair.State;
248
                    var file = pair.File;
249
                    if (fileState == null)
250
                    {
251
                        //This is a new file                        
252
                        var createState = FileState.CreateFor(file);
253
                        _persistenceAgent.Post(createState.Create);                        
254
                    }
255
                    else if (file == null)
256
                    {
257
                        //This file was deleted while we were down. We should mark it as deleted
258
                        //We have to go through UpdateStatus here because the state object we are using
259
                        //was created by a different ORM session.
260
                        _persistenceAgent.Post(() => UpdateStatusDirect(fileState.Id, FileStatus.Deleted));
261
                    }
262
                    else
263
                    {
264
                        //This file has a matching state. Need to check for possible changes
265
                        //To check for changes, we use the cheap (in CPU terms) SHA1 algorithm
266
                        //on the entire file.
267
                        
268
                        var hashString = file.ComputeShortHash(shortHasher);                        
269
                        //TODO: Need a way to attach the hashes to the filestate so we don't
270
                        //recalculate them each time a call to calculate has is made
271
                        //We can either store them to the filestate or add them to a 
272
                        //dictionary
273

    
274
                        //If the hashes don't match the file was changed
275
                        if (fileState.ShortHash != hashString)
276
                        {
277
                            _persistenceAgent.Post(() => UpdateStatusDirect(fileState.Id, FileStatus.Modified));
278
                        }
279
                    }
280
                }
281
            }
282
                        
283
         
284
        }
285
        
286

    
287

    
288
        private int UpdateStatusDirect(Guid id, FileStatus status)
289
        {
290
            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
291
            {
292

    
293
                try
294
                {
295
                    
296
                    using (var connection = GetConnection())
297
                    using (
298
                        var command = new SQLiteCommand("update FileState set FileStatus= :fileStatus where Id = :id  ",
299
                                                        connection))
300
                    {                                                
301
                        command.Parameters.AddWithValue("fileStatus", status);
302

    
303
                        command.Parameters.AddWithValue("id", id);
304
                        
305
                        var affected = command.ExecuteNonQuery();
306
                        
307
                        return affected;
308
                    }
309

    
310
                }
311
                catch (Exception exc)
312
                {
313
                    Log.Error(exc.ToString());
314
                    throw;
315
                }
316
            }
317
        }
318
        
319
        private int UpdateStatusDirect(string path, FileStatus status)
320
        {
321
            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
322
            {
323

    
324
                try
325
                {
326

    
327
                    
328
                    using (var connection = GetConnection())
329
                    using (
330
                        var command =
331
                            new SQLiteCommand("update FileState set FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE",
332
                                              connection))
333
                    {
334

    
335

    
336
                        command.Parameters.AddWithValue("fileStatus", status);
337

    
338
                        command.Parameters.AddWithValue("path", path);
339
                        
340
                        var affected = command.ExecuteNonQuery();
341
                        return affected;
342
                    }
343
                }
344
                catch (Exception exc)
345
                {
346
                    Log.Error(exc.ToString());
347
                    throw;
348
                }
349
            }
350
        }
351

    
352
        private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
353
        {
354
            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
355
            {
356

    
357
                try
358
                {
359

    
360
                    
361
                    using (var connection = GetConnection())
362
                    using (
363
                        var command =
364
                            new SQLiteCommand(
365
                                "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE ",
366
                                connection))
367
                    {
368

    
369
                        command.Parameters.AddWithValue("path", absolutePath);
370
                        command.Parameters.AddWithValue("fileStatus", fileStatus);
371
                        command.Parameters.AddWithValue("overlayStatus", overlayStatus);
372
                        
373
                        var affected = command.ExecuteNonQuery();
374
                        return affected;
375
                    }
376
                }
377
                catch (Exception exc)
378
                {
379
                    Log.Error(exc.ToString());
380
                    throw;
381
                }
382
            }
383
        }
384
        
385

    
386

    
387
        public string BlockHash { get; set; }
388

    
389
        public int BlockSize { get; set; }
390
        public void ChangeRoots(string oldPath, string newPath)
391
        {
392
            if (String.IsNullOrWhiteSpace(oldPath))
393
                throw new ArgumentNullException("oldPath");
394
            if (!Path.IsPathRooted(oldPath))
395
                throw new ArgumentException("oldPath must be an absolute path", "oldPath");
396
            if (string.IsNullOrWhiteSpace(newPath))
397
                throw new ArgumentNullException("newPath");
398
            if (!Path.IsPathRooted(newPath))
399
                throw new ArgumentException("newPath must be an absolute path", "newPath");
400
            Contract.EndContractBlock();
401

    
402
            FileState.ChangeRootPath(oldPath,newPath);
403

    
404
        }
405

    
406

    
407

    
408
        private readonly string _pithosDataPath;
409

    
410

    
411
        public FileState GetStateByFilePath(string path)
412
        {
413
            if (String.IsNullOrWhiteSpace(path))
414
                throw new ArgumentNullException("path");
415
            if (!Path.IsPathRooted(path))
416
                throw new ArgumentException("The path must be rooted", "path");
417
            Contract.EndContractBlock();
418

    
419
            try
420
            {
421
                
422
                using (var connection = GetConnection())
423
                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))
424
                {
425
                    
426
                    command.Parameters.AddWithValue("path", path);
427
                    
428
                    using (var reader = command.ExecuteReader())
429
                    {
430
                        if (reader.Read())
431
                        {
432
                            //var values = new object[reader.FieldCount];
433
                            //reader.GetValues(values);
434
                            var state = new FileState
435
                                            {
436
                                                Id = reader.GetGuid(0),
437
                                                FilePath = reader.IsDBNull(1)?"":reader.GetString(1),
438
                                                OverlayStatus =reader.IsDBNull(2)?FileOverlayStatus.Unversioned: (FileOverlayStatus) reader.GetInt64(2),
439
                                                FileStatus = reader.IsDBNull(3)?FileStatus.Missing:(FileStatus) reader.GetInt64(3),
440
                                                Checksum = reader.IsDBNull(4)?"":reader.GetString(4),
441
                                                ShortHash= reader.IsDBNull(5)?"":reader.GetString(5),
442
                                                Version = reader.IsDBNull(6)?default(long):reader.GetInt64(6),
443
                                                VersionTimeStamp = reader.IsDBNull(7)?default(DateTime):reader.GetDateTime(7),
444
                                                IsShared = !reader.IsDBNull(8) && reader.GetBoolean(8),
445
                                                SharedBy = reader.IsDBNull(9)?"":reader.GetString(9),
446
                                                ShareWrite = !reader.IsDBNull(10) && reader.GetBoolean(10)
447
                                            };
448
/*
449
                            var state = new FileState
450
                                            {
451
                                                Id = (Guid) values[0],
452
                                                FilePath = (string) values[1],
453
                                                OverlayStatus = (FileOverlayStatus) (long)values[2],
454
                                                FileStatus = (FileStatus) (long)values[3],
455
                                                Checksum = (string) values[4],
456
                                                Version = (long?) values[5],
457
                                                VersionTimeStamp = (DateTime?) values[6],
458
                                                IsShared = (long)values[7] == 1,
459
                                                SharedBy = (string) values[8],
460
                                                ShareWrite = (long)values[9] == 1
461
                                            };
462
*/
463
                            return state;
464
                        }
465
                        else
466
                        {
467
                            return null;
468
                        }
469

    
470
                    }                    
471
                }
472
            }
473
            catch (Exception exc)
474
            {
475
                Log.ErrorFormat(exc.ToString());
476
                throw;
477
            }            
478
        }
479

    
480
        public FileOverlayStatus GetFileOverlayStatus(string path)
481
        {
482
            if (String.IsNullOrWhiteSpace(path))
483
                throw new ArgumentNullException("path");
484
            if (!Path.IsPathRooted(path))
485
                throw new ArgumentException("The path must be rooted", "path");
486
            Contract.EndContractBlock();
487

    
488
            try
489
            {
490
                
491
                using (var connection = GetConnection())
492
                using (var command = new SQLiteCommand("select OverlayStatus from FileState where FilePath=:path  COLLATE NOCASE", connection))
493
                {
494
                    
495
                    command.Parameters.AddWithValue("path", path);
496
                    
497
                    var s = command.ExecuteScalar();
498
                    return (FileOverlayStatus) Convert.ToInt32(s);
499
                }
500
            }
501
            catch (Exception exc)
502
            {
503
                Log.ErrorFormat(exc.ToString());
504
                return FileOverlayStatus.Unversioned;
505
            }
506
        }
507

    
508
        private string GetConnectionString()
509
        {
510
            var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N;Pooling=True", _pithosDataPath);
511
            return connectionString;
512
        }
513

    
514
        private SQLiteConnection GetConnection()
515
        {
516
            var connectionString = GetConnectionString();
517
            var connection = new SQLiteConnection(connectionString);
518
            connection.Open();
519
            using(var cmd =connection.CreateCommand())
520
            {
521
                cmd.CommandText = "PRAGMA journal_mode=WAL";
522
                cmd.ExecuteNonQuery();
523
            }
524
            return connection;
525
        }
526

    
527
       /* public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus)
528
        {
529
            if (String.IsNullOrWhiteSpace(path))
530
                throw new ArgumentNullException("path");
531
            if (!Path.IsPathRooted(path))
532
                throw new ArgumentException("The path must be rooted","path");
533
            Contract.EndContractBlock();
534

    
535
            _persistenceAgent.Post(() => FileState.StoreOverlayStatus(path,overlayStatus));
536
        }*/
537

    
538
        public Task SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus, string shortHash = null)
539
        {
540
            if (String.IsNullOrWhiteSpace(path))
541
                throw new ArgumentNullException("path");
542
            if (!Path.IsPathRooted(path))
543
                throw new ArgumentException("The path must be rooted","path");
544
            Contract.EndContractBlock();
545

    
546
            return _persistenceAgent.PostAndAwait(() => FileState.StoreOverlayStatus(path,overlayStatus,shortHash));
547
        }
548

    
549
       /* public void RenameFileOverlayStatus(string oldPath, string newPath)
550
        {
551
            if (String.IsNullOrWhiteSpace(oldPath))
552
                throw new ArgumentNullException("oldPath");
553
            if (!Path.IsPathRooted(oldPath))
554
                throw new ArgumentException("The oldPath must be rooted", "oldPath");
555
            if (String.IsNullOrWhiteSpace(newPath))
556
                throw new ArgumentNullException("newPath");
557
            if (!Path.IsPathRooted(newPath))
558
                throw new ArgumentException("The newPath must be rooted", "newPath");
559
            Contract.EndContractBlock();
560

    
561
            _persistenceAgent.Post(() =>FileState.RenameState(oldPath, newPath));
562
        }*/
563

    
564
        public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus)
565
        {
566
            if (String.IsNullOrWhiteSpace(path))
567
                throw new ArgumentNullException("path");
568
            if (!Path.IsPathRooted(path))
569
                throw new ArgumentException("The path must be rooted", "path");
570
            Contract.EndContractBlock();
571

    
572
            Debug.Assert(!path.Contains(FolderConstants.CacheFolder));
573
            Debug.Assert(!path.EndsWith(".ignore"));
574

    
575
            _persistenceAgent.Post(() => UpdateStatusDirect(path, fileStatus, overlayStatus));
576
        }
577

    
578
/*
579
        public void StoreInfo(string path,ObjectInfo objectInfo)
580
        {
581
            if (String.IsNullOrWhiteSpace(path))
582
                throw new ArgumentNullException("path");
583
            if (!Path.IsPathRooted(path))
584
                throw new ArgumentException("The path must be rooted", "path");            
585
            if (objectInfo == null)
586
                throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");
587
            Contract.EndContractBlock();
588

    
589
            _persistenceAgent.Post(() =>
590
            {
591
                var filePath = path.ToLower();
592
                //Load the existing files state and set its properties in one session            
593
                using (new SessionScope())
594
                {
595
                    //Forgetting to use a sessionscope results in two sessions being created, one by 
596
                    //FirstOrDefault and one by Save()
597
                    var state =FileState.FindByFilePath(filePath);
598
                    
599
                    //Create a new empty state object if this is a new file
600
                    state = state ?? new FileState();
601

    
602
                    state.FilePath = filePath;
603
                    state.Checksum = objectInfo.Hash;
604
                    state.Version = objectInfo.Version;
605
                    state.VersionTimeStamp = objectInfo.VersionTimestamp;
606

    
607
                    state.FileStatus = FileStatus.Unchanged;
608
                    state.OverlayStatus = FileOverlayStatus.Normal;
609
                    
610
                  
611
                    //Do the save
612
                    state.Save();
613
                }
614
            });
615

    
616
        }
617
*/
618
        
619
        public void StoreInfo(string path, ObjectInfo objectInfo)
620
        {
621
            if (String.IsNullOrWhiteSpace(path))
622
                throw new ArgumentNullException("path");
623
            if (!Path.IsPathRooted(path))
624
                throw new ArgumentException("The path must be rooted", "path");
625
            if (objectInfo == null)
626
                throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");
627
            Contract.EndContractBlock();
628

    
629
            _persistenceAgent.Post(() => StoreInfoDirect(path, objectInfo));
630

    
631
        }
632

    
633
        private void StoreInfoDirect(string path, ObjectInfo objectInfo)
634
        {
635
            try
636
            {
637
                
638
                using (var connection = GetConnection())
639
                using (var command = new SQLiteCommand(connection))
640
                {
641
                    if (StateExists(path, connection))
642
                        command.CommandText =
643
                            "update FileState set FileStatus= :fileStatus where FilePath = :path  COLLATE NOCASE ";
644
                    else
645
                    {
646
                        command.CommandText =
647
                            "INSERT INTO FileState (Id,FilePath,Checksum,Version,VersionTimeStamp,ShortHash,FileStatus,OverlayStatus) VALUES (:id,:path,:checksum,:version,:versionTimeStamp,:shortHash,:fileStatus,:overlayStatus)";
648
                        command.Parameters.AddWithValue("id", Guid.NewGuid());
649
                    }
650

    
651
                    command.Parameters.AddWithValue("path", path);
652
                    command.Parameters.AddWithValue("checksum", objectInfo.Hash);
653
                    command.Parameters.AddWithValue("shortHash", "");
654
                    command.Parameters.AddWithValue("version", objectInfo.Version);
655
                    command.Parameters.AddWithValue("versionTimeStamp",
656
                                                    objectInfo.VersionTimestamp);
657
                    command.Parameters.AddWithValue("fileStatus", FileStatus.Unchanged);
658
                    command.Parameters.AddWithValue("overlayStatus",
659
                                                    FileOverlayStatus.Normal);
660

    
661
                    var affected = command.ExecuteNonQuery();
662
                    return;
663
                }
664
            }
665
            catch (Exception exc)
666
            {
667
                Log.Error(exc.ToString());
668
                throw;
669
            }
670
        }
671

    
672
        private bool StateExists(string filePath,SQLiteConnection connection)
673
        {
674
            using (var command = new SQLiteCommand("Select count(*) from FileState where FilePath=:path  COLLATE NOCASE", connection))
675
            {
676
                command.Parameters.AddWithValue("path", filePath);
677
                var result = command.ExecuteScalar();
678
                return ((long)result >= 1);
679
            }
680

    
681
        }
682

    
683
        public void SetFileStatus(string path, FileStatus status)
684
        {
685
            if (String.IsNullOrWhiteSpace(path))
686
                throw new ArgumentNullException("path");
687
            if (!Path.IsPathRooted(path))
688
                throw new ArgumentException("The path must be rooted", "path");
689
            Contract.EndContractBlock();
690
            
691
            _persistenceAgent.Post(() => UpdateStatusDirect(path, status));
692
        }
693

    
694
        public FileStatus GetFileStatus(string path)
695
        {
696
            if (String.IsNullOrWhiteSpace(path))
697
                throw new ArgumentNullException("path");
698
            if (!Path.IsPathRooted(path))
699
                throw new ArgumentException("The path must be rooted", "path");
700
            Contract.EndContractBlock();
701

    
702
            
703
            using (var connection = GetConnection())
704
            {
705
                var command = new SQLiteCommand("select FileStatus from FileState where FilePath=:path  COLLATE NOCASE", connection);
706
                command.Parameters.AddWithValue("path", path);
707
                
708
                var statusValue = command.ExecuteScalar();
709
                if (statusValue==null)
710
                    return FileStatus.Missing;
711
                return (FileStatus)Convert.ToInt32(statusValue);
712
            }
713
        }
714

    
715
        /// <summary>
716
        /// Deletes the status of the specified file
717
        /// </summary>
718
        /// <param name="path"></param>
719
        public void ClearFileStatus(string path)
720
        {
721
            if (String.IsNullOrWhiteSpace(path))
722
                throw new ArgumentNullException("path");
723
            if (!Path.IsPathRooted(path))
724
                throw new ArgumentException("The path must be rooted", "path");
725
            Contract.EndContractBlock();
726

    
727
            _persistenceAgent.Post(() => DeleteDirect(path));   
728
        }
729

    
730
        /// <summary>
731
        /// Deletes the status of the specified folder and all its contents
732
        /// </summary>
733
        /// <param name="path"></param>
734
        public void ClearFolderStatus(string path)
735
        {
736
            if (String.IsNullOrWhiteSpace(path))
737
                throw new ArgumentNullException("path");
738
            if (!Path.IsPathRooted(path))
739
                throw new ArgumentException("The path must be rooted", "path");
740
            Contract.EndContractBlock();
741

    
742
            _persistenceAgent.Post(() => DeleteFolderDirect(path));   
743
        }
744

    
745
        public IEnumerable<FileState> GetChildren(FileState fileState)
746
        {
747
            if (fileState == null)
748
                throw new ArgumentNullException("fileState");
749
            Contract.EndContractBlock();
750

    
751
            var children = from state in FileState.Queryable
752
                           where state.FilePath.StartsWith(fileState.FilePath + "\\")
753
                           select state;
754
            return children;
755
        }
756

    
757
        public void EnsureFileState(string path)
758
        {
759
            var existingState = GetStateByFilePath(path);
760
            if (existingState != null)
761
                return;
762
            var fileInfo = FileInfoExtensions.FromPath(path);
763
            using (new SessionScope())
764
            {
765
                var newState = FileState.CreateFor(fileInfo);
766
                newState.FileStatus=FileStatus.Missing;
767
                _persistenceAgent.PostAndAwait(newState.CreateAndFlush).Wait();
768
            }
769

    
770
        }
771

    
772
        private int DeleteDirect(string filePath)
773
        {
774
            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))
775
            {
776

    
777
                try
778
                {
779

    
780
                    
781
                    using (var connection = GetConnection())
782
                    {
783
                        var command = new SQLiteCommand("delete from FileState where FilePath = :path  COLLATE NOCASE",
784
                                                        connection);
785

    
786
                        command.Parameters.AddWithValue("path", filePath);
787
                        
788
                        var affected = command.ExecuteNonQuery();
789
                        return affected;
790
                    }
791
                }
792
                catch (Exception exc)
793
                {
794
                    Log.Error(exc.ToString());
795
                    throw;
796
                }
797
            }
798
        }
799

    
800
        private int DeleteFolderDirect(string filePath)
801
        {
802
            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))
803
            {
804

    
805
                try
806
                {
807

    
808
                    
809
                    using (var connection = GetConnection())
810
                    {
811
                        var command = new SQLiteCommand(@"delete from FileState where FilePath = :path or FilePath like :path || '\%'  COLLATE NOCASE",
812
                                                        connection);
813

    
814
                        command.Parameters.AddWithValue("path", filePath);
815
                        
816
                        var affected = command.ExecuteNonQuery();
817
                        return affected;
818
                    }
819
                }
820
                catch (Exception exc)
821
                {
822
                    Log.Error(exc.ToString());
823
                    throw;
824
                }
825
            }
826
        }
827

    
828
        public void UpdateFileChecksum(string path, string shortHash, string checksum)
829
        {
830
            if (String.IsNullOrWhiteSpace(path))
831
                throw new ArgumentNullException("path");
832
            if (!Path.IsPathRooted(path))
833
                throw new ArgumentException("The path must be rooted", "path");            
834
            Contract.EndContractBlock();
835

    
836
            _persistenceAgent.Post(() => FileState.UpdateChecksum(path, shortHash,checksum));
837
        }
838

    
839
    }
840

    
841
   
842
}