Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / StatusAgent.cs @ 6bcdd8e2

History | View | Annotate | Download (32.1 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.Text;
52
using System.Threading;
53
using System.Threading.Tasks;
54
using Castle.ActiveRecord;
55
using Castle.ActiveRecord.Framework.Config;
56
using Pithos.Interfaces;
57
using Pithos.Network;
58
using log4net;
59

    
60
namespace Pithos.Core.Agents
61
{
62
    [Export(typeof(IStatusChecker)),Export(typeof(IStatusKeeper))]
63
    public class StatusAgent:IStatusChecker,IStatusKeeper
64
    {
65
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
66

    
67
        [System.ComponentModel.Composition.Import]
68
        public IPithosSettings Settings { get; set; }
69

    
70
        private Agent<Action> _persistenceAgent;
71

    
72

    
73

    
74
        public StatusAgent()
75
        {            
76
            var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
77
            
78

    
79

    
80
            _pithosDataPath = Path.Combine(appDataPath , "GRNET");
81
            if (!Directory.Exists(_pithosDataPath))
82
                Directory.CreateDirectory(_pithosDataPath);
83

    
84
            var dbPath = Path.Combine(_pithosDataPath, "pithos.db");
85

    
86
            MigrateOldDb(dbPath, appDataPath);
87

    
88
            var source = GetConfiguration(_pithosDataPath);
89
            ActiveRecordStarter.Initialize(source,typeof(FileState),typeof(FileTag));
90
            ActiveRecordStarter.UpdateSchema();
91

    
92

    
93
            if (!File.Exists(dbPath))
94
                ActiveRecordStarter.CreateSchema();
95

    
96
            CreateTrigger();
97
            
98
        }
99

    
100

    
101
        private static void MigrateOldDb(string dbPath, string appDataPath)
102
        {
103
            Contract.Requires(!String.IsNullOrWhiteSpace(dbPath));
104
            Contract.Requires(!String.IsNullOrWhiteSpace(appDataPath));
105

    
106
            var oldDbPath = Path.Combine(appDataPath, "Pithos", "pithos.db");
107
            var oldDbInfo = new FileInfo(oldDbPath);
108
            if (oldDbInfo.Exists && !File.Exists(dbPath))
109
            {
110
                var oldDirectory = oldDbInfo.Directory;
111
                oldDbInfo.MoveTo(dbPath);                
112
                oldDirectory.Delete(true);
113
            }
114
        }
115

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

    
136

    
137
        private static InPlaceConfigurationSource GetConfiguration(string pithosDbPath)
138
        {
139
            if (String.IsNullOrWhiteSpace(pithosDbPath))
140
                throw new ArgumentNullException("pithosDbPath");
141
            if (!Path.IsPathRooted(pithosDbPath))
142
                throw new ArgumentException("path must be a rooted path", "pithosDbPath");
143
            Contract.EndContractBlock();
144

    
145
            var properties = new Dictionary<string, string>
146
                                 {
147
                                     {"connection.driver_class", "NHibernate.Driver.SQLite20Driver"},
148
                                     {"dialect", "NHibernate.Dialect.SQLiteDialect"},
149
                                     {"connection.provider", "NHibernate.Connection.DriverConnectionProvider"},
150
                                     {
151
                                         "proxyfactory.factory_class",
152
                                         "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"
153
                                         },
154
                                 };
155

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

    
159
            var source = new InPlaceConfigurationSource();                        
160
            source.Add(typeof (ActiveRecordBase), properties);
161
            source.SetDebugFlag(false);            
162
            return source;
163
        }
164

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

    
198
       
199

    
200
        public void Stop()
201
        {
202
            _persistenceAgent.Stop();            
203
        }
204
       
205

    
206
        public void ProcessExistingFiles(IEnumerable<FileInfo> existingFiles)
207
        {
208
            if(existingFiles  ==null)
209
                throw new ArgumentNullException("existingFiles");
210
            Contract.EndContractBlock();
211
            
212
            //Find new or matching files with a left join to the stored states
213
            var fileStates = FileState.Queryable;
214
            var currentFiles=from file in existingFiles
215
                      join state in fileStates on file.FullName.ToLower() equals state.FilePath.ToLower() into gs
216
                      from substate in gs.DefaultIfEmpty()
217
                               select new {File = file, State = substate};
218

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

    
233
            var pairs = currentFiles.Union(deletedFiles);
234

    
235
            foreach(var pair in pairs)
236
            {
237
                var fileState = pair.State;
238
                var file = pair.File;
239
                if (fileState == null)
240
                {
241
                    //This is a new file
242
                    var fullPath = pair.File.FullName;
243
                    var createState = FileState.CreateForAsync(fullPath, BlockSize, BlockHash);
244
                    createState.ContinueWith(state => _persistenceAgent.Post(state.Result.Create));
245
                }                
246
                else if (file == null)
247
                {
248
                    //This file was deleted while we were down. We should mark it as deleted
249
                    //We have to go through UpdateStatus here because the state object we are using
250
                    //was created by a different ORM session.
251
                    _persistenceAgent.Post(()=> UpdateStatusDirect(fileState.Id, FileStatus.Deleted));                    
252
                }
253
                else
254
                {
255
                    //This file has a matching state. Need to check for possible changes
256
                    var hashString = file.CalculateHash(BlockSize,BlockHash);
257
                    //TODO: Need a way to attach the hashes to the filestate so we don't
258
                    //recalculate them each time a call to calculate has is made
259
                    //We can either store them to the filestate or add them to a 
260
                    //dictionary
261

    
262
                    //If the hashes don't match the file was changed
263
                    if (fileState.Checksum != hashString)
264
                    {
265
                        _persistenceAgent.Post(() => UpdateStatusDirect(fileState.Id, FileStatus.Modified));
266
                    }                    
267
                }
268
            };            
269
         
270
        }
271

    
272
        private int UpdateStatusDirect(Guid id, FileStatus status)
273
        {
274
            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
275
            {
276

    
277
                try
278
                {
279
                    
280
                    using (var connection = GetConnection())
281
                    using (
282
                        var command = new SQLiteCommand("update FileState set FileStatus= :fileStatus where Id = :id  ",
283
                                                        connection))
284
                    {                                                
285
                        command.Parameters.AddWithValue("fileStatus", status);
286

    
287
                        command.Parameters.AddWithValue("id", id);
288
                        
289
                        var affected = command.ExecuteNonQuery();
290
                        
291
                        return affected;
292
                    }
293

    
294
                }
295
                catch (Exception exc)
296
                {
297
                    Log.Error(exc.ToString());
298
                    throw;
299
                }
300
            }
301
        }
302
        
303
        private int UpdateStatusDirect(string path, FileStatus status)
304
        {
305
            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
306
            {
307

    
308
                try
309
                {
310

    
311
                    
312
                    using (var connection = GetConnection())
313
                    using (
314
                        var command =
315
                            new SQLiteCommand("update FileState set FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE",
316
                                              connection))
317
                    {
318

    
319

    
320
                        command.Parameters.AddWithValue("fileStatus", status);
321

    
322
                        command.Parameters.AddWithValue("path", path);
323
                        
324
                        var affected = command.ExecuteNonQuery();
325
                        return affected;
326
                    }
327
                }
328
                catch (Exception exc)
329
                {
330
                    Log.Error(exc.ToString());
331
                    throw;
332
                }
333
            }
334
        }
335

    
336
        private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
337
        {
338
            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
339
            {
340

    
341
                try
342
                {
343

    
344
                    
345
                    using (var connection = GetConnection())
346
                    using (
347
                        var command =
348
                            new SQLiteCommand(
349
                                "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE ",
350
                                connection))
351
                    {
352

    
353
                        command.Parameters.AddWithValue("path", absolutePath);
354
                        command.Parameters.AddWithValue("fileStatus", fileStatus);
355
                        command.Parameters.AddWithValue("overlayStatus", overlayStatus);
356
                        
357
                        var affected = command.ExecuteNonQuery();
358
                        return affected;
359
                    }
360
                }
361
                catch (Exception exc)
362
                {
363
                    Log.Error(exc.ToString());
364
                    throw;
365
                }
366
            }
367
        }
368
        
369

    
370

    
371
        public string BlockHash { get; set; }
372

    
373
        public int BlockSize { get; set; }
374
        public void ChangeRoots(string oldPath, string newPath)
375
        {
376
            if (String.IsNullOrWhiteSpace(oldPath))
377
                throw new ArgumentNullException("oldPath");
378
            if (!Path.IsPathRooted(oldPath))
379
                throw new ArgumentException("oldPath must be an absolute path", "oldPath");
380
            if (string.IsNullOrWhiteSpace(newPath))
381
                throw new ArgumentNullException("newPath");
382
            if (!Path.IsPathRooted(newPath))
383
                throw new ArgumentException("newPath must be an absolute path", "newPath");
384
            Contract.EndContractBlock();
385

    
386
            FileState.ChangeRootPath(oldPath,newPath);
387

    
388
        }
389

    
390
        private PithosStatus _pithosStatus=PithosStatus.InSynch;       
391

    
392
        public void SetPithosStatus(PithosStatus status)
393
        {
394
            _pithosStatus = status;
395
        }
396

    
397
        public PithosStatus GetPithosStatus()
398
        {
399
            return _pithosStatus;
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   ,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
                                                Version = reader.IsDBNull(5)?default(long):reader.GetInt64(5),
437
                                                VersionTimeStamp = reader.IsDBNull(6)?default(DateTime):reader.GetDateTime(6),
438
                                                IsShared = !reader.IsDBNull(7) && reader.GetBoolean(7),
439
                                                SharedBy = reader.IsDBNull(8)?"":reader.GetString(8),
440
                                                ShareWrite = !reader.IsDBNull(9) && reader.GetBoolean(9)
441
                                            };
442
/*
443
                            var state = new FileState
444
                                            {
445
                                                Id = (Guid) values[0],
446
                                                FilePath = (string) values[1],
447
                                                OverlayStatus = (FileOverlayStatus) (long)values[2],
448
                                                FileStatus = (FileStatus) (long)values[3],
449
                                                Checksum = (string) values[4],
450
                                                Version = (long?) values[5],
451
                                                VersionTimeStamp = (DateTime?) values[6],
452
                                                IsShared = (long)values[7] == 1,
453
                                                SharedBy = (string) values[8],
454
                                                ShareWrite = (long)values[9] == 1
455
                                            };
456
*/
457
                            return state;
458
                        }
459
                        else
460
                        {
461
                            return null;
462
                        }
463

    
464
                    }                    
465
                }
466
            }
467
            catch (Exception exc)
468
            {
469
                Log.ErrorFormat(exc.ToString());
470
                throw;
471
            }            
472
        }
473

    
474
        public FileOverlayStatus GetFileOverlayStatus(string path)
475
        {
476
            if (String.IsNullOrWhiteSpace(path))
477
                throw new ArgumentNullException("path");
478
            if (!Path.IsPathRooted(path))
479
                throw new ArgumentException("The path must be rooted", "path");
480
            Contract.EndContractBlock();
481

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

    
502
        private string GetConnectionString()
503
        {
504
            var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N;Pooling=True", _pithosDataPath);
505
            return connectionString;
506
        }
507

    
508
        private SQLiteConnection GetConnection()
509
        {
510
            var connectionString = GetConnectionString();
511
            var connection = new SQLiteConnection(connectionString);
512
            connection.Open();
513
            using(var cmd =connection.CreateCommand())
514
            {
515
                cmd.CommandText = "PRAGMA journal_mode=WAL";
516
                cmd.ExecuteNonQuery();
517
            }
518
            return connection;
519
        }
520

    
521
        public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus)
522
        {
523
            if (String.IsNullOrWhiteSpace(path))
524
                throw new ArgumentNullException("path");
525
            if (!Path.IsPathRooted(path))
526
                throw new ArgumentException("The path must be rooted","path");
527
            Contract.EndContractBlock();
528

    
529
            _persistenceAgent.Post(() => FileState.StoreOverlayStatus(path,overlayStatus));
530
        }
531

    
532
       /* public void RenameFileOverlayStatus(string oldPath, string newPath)
533
        {
534
            if (String.IsNullOrWhiteSpace(oldPath))
535
                throw new ArgumentNullException("oldPath");
536
            if (!Path.IsPathRooted(oldPath))
537
                throw new ArgumentException("The oldPath must be rooted", "oldPath");
538
            if (String.IsNullOrWhiteSpace(newPath))
539
                throw new ArgumentNullException("newPath");
540
            if (!Path.IsPathRooted(newPath))
541
                throw new ArgumentException("The newPath must be rooted", "newPath");
542
            Contract.EndContractBlock();
543

    
544
            _persistenceAgent.Post(() =>FileState.RenameState(oldPath, newPath));
545
        }*/
546

    
547
        public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus)
548
        {
549
            if (String.IsNullOrWhiteSpace(path))
550
                throw new ArgumentNullException("path");
551
            if (!Path.IsPathRooted(path))
552
                throw new ArgumentException("The path must be rooted", "path");
553
            Contract.EndContractBlock();
554

    
555
            Debug.Assert(!path.Contains(FolderConstants.CacheFolder));
556
            Debug.Assert(!path.EndsWith(".ignore"));
557

    
558
            _persistenceAgent.Post(() => UpdateStatusDirect(path, fileStatus, overlayStatus));
559
        }
560

    
561
/*
562
        public void StoreInfo(string path,ObjectInfo objectInfo)
563
        {
564
            if (String.IsNullOrWhiteSpace(path))
565
                throw new ArgumentNullException("path");
566
            if (!Path.IsPathRooted(path))
567
                throw new ArgumentException("The path must be rooted", "path");            
568
            if (objectInfo == null)
569
                throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");
570
            Contract.EndContractBlock();
571

    
572
            _persistenceAgent.Post(() =>
573
            {
574
                var filePath = path.ToLower();
575
                //Load the existing files state and set its properties in one session            
576
                using (new SessionScope())
577
                {
578
                    //Forgetting to use a sessionscope results in two sessions being created, one by 
579
                    //FirstOrDefault and one by Save()
580
                    var state =FileState.FindByFilePath(filePath);
581
                    
582
                    //Create a new empty state object if this is a new file
583
                    state = state ?? new FileState();
584

    
585
                    state.FilePath = filePath;
586
                    state.Checksum = objectInfo.Hash;
587
                    state.Version = objectInfo.Version;
588
                    state.VersionTimeStamp = objectInfo.VersionTimestamp;
589

    
590
                    state.FileStatus = FileStatus.Unchanged;
591
                    state.OverlayStatus = FileOverlayStatus.Normal;
592
                    
593
                  
594
                    //Do the save
595
                    state.Save();
596
                }
597
            });
598

    
599
        }
600
*/
601
        
602
        public void StoreInfo(string path, ObjectInfo objectInfo)
603
        {
604
            if (String.IsNullOrWhiteSpace(path))
605
                throw new ArgumentNullException("path");
606
            if (!Path.IsPathRooted(path))
607
                throw new ArgumentException("The path must be rooted", "path");
608
            if (objectInfo == null)
609
                throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");
610
            Contract.EndContractBlock();
611

    
612
            _persistenceAgent.Post(() => StoreInfoDirect(path, objectInfo));
613

    
614
        }
615

    
616
        private void StoreInfoDirect(string path, ObjectInfo objectInfo)
617
        {
618
            try
619
            {
620
                
621
                using (var connection = GetConnection())
622
                using (var command = new SQLiteCommand(connection))
623
                {
624
                    if (StateExists(path, connection))
625
                        command.CommandText =
626
                            "update FileState set FileStatus= :fileStatus where FilePath = :path  COLLATE NOCASE ";
627
                    else
628
                    {
629
                        command.CommandText =
630
                            "INSERT INTO FileState (Id,FilePath,Checksum,Version,VersionTimeStamp,FileStatus,OverlayStatus) VALUES (:id,:path,:checksum,:version,:versionTimeStamp,:fileStatus,:overlayStatus)";
631
                        command.Parameters.AddWithValue("id", Guid.NewGuid());
632
                    }
633

    
634
                    command.Parameters.AddWithValue("path", path);
635
                    command.Parameters.AddWithValue("checksum", objectInfo.Hash);
636
                    command.Parameters.AddWithValue("version", objectInfo.Version);
637
                    command.Parameters.AddWithValue("versionTimeStamp",
638
                                                    objectInfo.VersionTimestamp);
639
                    command.Parameters.AddWithValue("fileStatus", FileStatus.Unchanged);
640
                    command.Parameters.AddWithValue("overlayStatus",
641
                                                    FileOverlayStatus.Normal);
642

    
643
                    var affected = command.ExecuteNonQuery();
644
                    return;
645
                }
646
            }
647
            catch (Exception exc)
648
            {
649
                Log.Error(exc.ToString());
650
                throw;
651
            }
652
        }
653

    
654
        private bool StateExists(string filePath,SQLiteConnection connection)
655
        {
656
            using (var command = new SQLiteCommand("Select count(*) from FileState where FilePath=:path  COLLATE NOCASE", connection))
657
            {
658
                command.Parameters.AddWithValue("path", filePath);
659
                var result = command.ExecuteScalar();
660
                return ((long)result >= 1);
661
            }
662

    
663
        }
664

    
665
        public void SetFileStatus(string path, FileStatus status)
666
        {
667
            if (String.IsNullOrWhiteSpace(path))
668
                throw new ArgumentNullException("path");
669
            if (!Path.IsPathRooted(path))
670
                throw new ArgumentException("The path must be rooted", "path");
671
            Contract.EndContractBlock();
672
            
673
            _persistenceAgent.Post(() => UpdateStatusDirect(path, status));
674
        }
675

    
676
        public FileStatus GetFileStatus(string path)
677
        {
678
            if (String.IsNullOrWhiteSpace(path))
679
                throw new ArgumentNullException("path");
680
            if (!Path.IsPathRooted(path))
681
                throw new ArgumentException("The path must be rooted", "path");
682
            Contract.EndContractBlock();
683

    
684
            
685
            using (var connection = GetConnection())
686
            {
687
                var command = new SQLiteCommand("select FileStatus from FileState where FilePath=:path  COLLATE NOCASE", connection);
688
                command.Parameters.AddWithValue("path", path);
689
                
690
                var statusValue = command.ExecuteScalar();
691
                if (statusValue==null)
692
                    return FileStatus.Missing;
693
                return (FileStatus)Convert.ToInt32(statusValue);
694
            }
695
        }
696

    
697
        /// <summary>
698
        /// Deletes the status of the specified file
699
        /// </summary>
700
        /// <param name="path"></param>
701
        public void ClearFileStatus(string path)
702
        {
703
            if (String.IsNullOrWhiteSpace(path))
704
                throw new ArgumentNullException("path");
705
            if (!Path.IsPathRooted(path))
706
                throw new ArgumentException("The path must be rooted", "path");
707
            Contract.EndContractBlock();
708

    
709
            _persistenceAgent.Post(() => DeleteDirect(path));   
710
        }
711

    
712
        /// <summary>
713
        /// Deletes the status of the specified folder and all its contents
714
        /// </summary>
715
        /// <param name="path"></param>
716
        public void ClearFolderStatus(string path)
717
        {
718
            if (String.IsNullOrWhiteSpace(path))
719
                throw new ArgumentNullException("path");
720
            if (!Path.IsPathRooted(path))
721
                throw new ArgumentException("The path must be rooted", "path");
722
            Contract.EndContractBlock();
723

    
724
            _persistenceAgent.Post(() => DeleteFolderDirect(path));   
725
        }
726

    
727
        public IEnumerable<FileState> GetChildren(FileState fileState)
728
        {
729
            if (fileState == null)
730
                throw new ArgumentNullException("fileState");
731
            Contract.EndContractBlock();
732

    
733
            var children = from state in FileState.Queryable
734
                           where state.FilePath.StartsWith(fileState.FilePath + "\\")
735
                           select state;
736
            return children;
737
        }
738

    
739
        private int DeleteDirect(string filePath)
740
        {
741
            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))
742
            {
743

    
744
                try
745
                {
746

    
747
                    
748
                    using (var connection = GetConnection())
749
                    {
750
                        var command = new SQLiteCommand("delete from FileState where FilePath = :path  COLLATE NOCASE",
751
                                                        connection);
752

    
753
                        command.Parameters.AddWithValue("path", filePath);
754
                        
755
                        var affected = command.ExecuteNonQuery();
756
                        return affected;
757
                    }
758
                }
759
                catch (Exception exc)
760
                {
761
                    Log.Error(exc.ToString());
762
                    throw;
763
                }
764
            }
765
        }
766

    
767
        private int DeleteFolderDirect(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 or FilePath like :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
        public void UpdateFileChecksum(string path, string checksum)
796
        {
797
            if (String.IsNullOrWhiteSpace(path))
798
                throw new ArgumentNullException("path");
799
            if (!Path.IsPathRooted(path))
800
                throw new ArgumentException("The path must be rooted", "path");            
801
            Contract.EndContractBlock();
802

    
803
            _persistenceAgent.Post(() => FileState.UpdateChecksum(path, checksum));
804
        }
805

    
806
    }
807

    
808
   
809
}