Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / FileState.cs @ 12c87c0e

History | View | Annotate | Download (24.3 kB)

1
#region
2
/* -----------------------------------------------------------------------
3
 * <copyright file="FileState.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.Diagnostics.Contracts;
43
using System.IO;
44
using System.Reflection;
45
using System.Threading.Tasks;
46
using Castle.ActiveRecord;
47
using Castle.ActiveRecord.Framework;
48
using Castle.ActiveRecord.Queries;
49
using NHibernate.Criterion;
50
using Pithos.Core.Agents;
51
using Pithos.Interfaces;
52
using Pithos.Network;
53
using log4net;
54

    
55
namespace Pithos.Core
56
{
57
    using System;
58
    using System.Collections.Generic;    
59

    
60
    /// <summary>
61
    /// TODO: Update summary.
62
    /// </summary>
63
    [ActiveRecord]
64
    public class FileState : ActiveRecordLinqBase<FileState>
65
    {
66
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
67

    
68

    
69
        private IList<FileTag> _tags = new List<FileTag>();
70

    
71
        [PrimaryKey(PrimaryKeyType.Guid)]
72
        public Guid Id { get; set; }
73

    
74
        
75
        //[Property(Unique = true, UniqueKey = "IX_FileState_ObjectID")]
76
        [Property]
77
        public string ObjectID { get; set; }
78

    
79
        [Property(Unique = true, UniqueKey = "IX_FileState_FilePath")]
80
        public string FilePath { get; set; }
81

    
82
        [Property]
83
        public FileOverlayStatus OverlayStatus { get; set; }
84

    
85
        [Property]
86
        public FileStatus FileStatus { get; set; }
87

    
88
        [Property]
89
        public string ConflictReason { get; set; }
90

    
91
        private string _checksum;
92

    
93
        /// <summary>
94
        /// The tophash value of the file, calculated by using a Merkle hash with the SHA256 algorithm
95
        /// </summary>
96
        /// <remarks>
97
        /// The SHA256 algorithm is substantially more expenive than other algorithms.
98
        /// Recalculating the Checksum should be avoided whenever possible.
99
        /// </remarks>
100
        [Property]
101
        public string Checksum
102
        {
103
            get
104
            {
105
                return _checksum;
106
            }
107
            set
108
            {
109
                _checksum = value;
110
            }
111
        }
112
        
113
        /// <summary>
114
        /// An easy to calcualte hash over the entire file, used to detect file changes
115
        /// </summary>
116
        /// <remarks>The algorithm used to calculate this hash should be cheap</remarks>
117
        [Property(NotNull = true, Default = "")]
118
        public string ETag { get; set; }
119

    
120
        [Property(NotNull = true, Default = "")]
121
        public string LastMD5 { get; set; }
122

    
123
        [Property]
124
        public string Hashes { get; set; }
125

    
126
        [Property]
127
        public DateTime? LastWriteDate { get; set; }
128

    
129
        [Property]
130
        public long? LastLength { get; set; }
131
        
132
        [Property]
133
        public long? Version { get; set; }
134

    
135
        [Property]
136
        public DateTime? VersionTimeStamp { get; set; }
137

    
138

    
139
        [Property]
140
        public bool IsShared { get; set; }
141

    
142
        [Property]
143
        public string SharedBy { get; set; }
144

    
145
        [Property]
146
        public bool ShareWrite { get; set; }
147

    
148
        [Property]
149
        public bool IsFolder{ get; set; }
150

    
151
        [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true, Inverse = true)]
152
        public IList<FileTag> Tags
153
        {
154
            get { return _tags; }
155
            set { _tags = value; }
156
        }
157

    
158
        [Property]
159
        public DateTime Modified { get; set; }
160

    
161

    
162
        public FileSystemInfo GetFileSystemInfo()
163
        {
164
            if (String.IsNullOrWhiteSpace(FilePath))
165
                throw new InvalidOperationException();
166
            Contract.EndContractBlock();
167

    
168
            return Directory.Exists(FilePath) ?
169
                (FileSystemInfo)new DirectoryInfo(FilePath)
170
                : new FileInfo(FilePath);
171
        }
172

    
173
        public string GetRelativeUrl(AccountInfo accountInfo)
174
        {
175
            if (accountInfo==null)
176
                throw new ArgumentNullException("accountInfo");
177
            Contract.EndContractBlock();
178

    
179
            var fsi=GetFileSystemInfo();
180
            return fsi.AsRelativeUrlTo(accountInfo.AccountPath);
181
        }
182
        /*public static FileState FindByFilePath(string absolutePath)
183
        {
184
            if (string.IsNullOrWhiteSpace(absolutePath))
185
                throw new ArgumentNullException("absolutePath");
186
            Contract.EndContractBlock();
187
            try
188
            {
189

    
190
                
191

    
192
                return Queryable.FirstOrDefault(s => s.FilePath == absolutePath);
193
            }
194
            catch (Exception ex)
195
            {
196
                Log.Error(ex.ToString());
197
                throw;
198
            }
199

    
200

    
201
        }*/
202

    
203
       /* public static void DeleteByFilePath(string absolutePath)
204
        {
205
            if (string.IsNullOrWhiteSpace(absolutePath))
206
                throw new ArgumentNullException("absolutePath");
207
            Contract.EndContractBlock();
208

    
209
            ExecuteWithRetry((session, instance) =>
210
                        {
211
                            const string hqlDelete = "delete FileState where FilePath = :path";
212
                            var deletedEntities = session.CreateQuery(hqlDelete)
213
                                .SetString("path", absolutePath)
214
                                .ExecuteUpdate();
215
                            return deletedEntities;
216
                        }, null);
217

    
218
        }*/
219

    
220
/*
221
        public static void StoreFileStatus(string absolutePath, FileStatus newStatus)
222
        {
223
            if (string.IsNullOrWhiteSpace(absolutePath))
224
                throw new ArgumentNullException("absolutePath");
225
            Contract.EndContractBlock();
226

    
227
            ExecuteWithRetry((session, instance) =>
228
                                 {
229
                                     const string hqlUpdate =
230
                                         "update FileState set FileStatus= :status where FilePath = :path ";
231
                                     using (session.BeginTransaction())
232
                                     {
233
                                         var updatedEntities = session.CreateQuery(hqlUpdate)
234
                                             .SetString("path", absolutePath)
235
                                             .SetEnum("status", newStatus)
236
                                             .ExecuteUpdate();
237
                                         if (updatedEntities == 0)
238
                                         {
239
                                             var newState = new FileState
240
                                                                {
241
                                                                    FilePath = absolutePath,
242
                                                                    Id = Guid.NewGuid(),
243
                                                                    FileStatus = newStatus,
244
                                                                    IsFolder = Directory.Exists(absolutePath)
245
                                                                };
246
                                             newState.CreateAndFlush();
247
                                         }
248
                                         return null;
249
                                     }
250
                                 }, null);
251

    
252
        }
253
*/
254

    
255
        public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus,string etag)
256
        {
257
            if (string.IsNullOrWhiteSpace(absolutePath))
258
                throw new ArgumentNullException("absolutePath");
259
            Contract.EndContractBlock();
260

    
261
            ExecuteWithRetry((session, instance) =>
262
                        {
263
                            const string hqlUpdate =
264
                                "update FileState set OverlayStatus= :status where FilePath = :path ";
265
                            using (var tx=session.BeginTransaction())
266
                            {
267
                                var updatedEntities = session.CreateQuery(hqlUpdate)
268
                                    .SetString("path", absolutePath)
269
                                    .SetEnum("status", newStatus)
270
                                    .ExecuteUpdate();
271
                                if (updatedEntities == 0)
272
                                {
273
                                    var newState = new FileState
274
                                                       {
275
                                                           FilePath = absolutePath,
276
                                                           Id = Guid.NewGuid(),
277
                                                           OverlayStatus = newStatus,
278
                                                           ETag = etag ?? String.Empty,
279
                                                           LastMD5=String.Empty,
280
                                                           IsFolder = Directory.Exists(absolutePath)
281
                                                       };
282
                                    newState.CreateAndFlush();
283
                                }
284
                                tx.Commit();
285
                                return null;
286
                            }
287
                        }, null);
288

    
289
        }
290

    
291
/*
292
        public static void UpdateStatus(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
293
        {
294
            if (string.IsNullOrWhiteSpace(absolutePath))
295
                throw new ArgumentNullException("absolutePath");
296
            Contract.EndContractBlock();
297

    
298
            ExecuteWithRetry((session, instance) =>
299
                        {
300
                            const string hqlUpdate =
301
                                "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path  ";
302
                            var updatedEntities = session.CreateQuery(hqlUpdate)
303
                                .SetString("path", absolutePath)
304
                                .SetEnum("fileStatus", fileStatus)
305
                                .SetEnum("overlayStatus", overlayStatus)
306
                                .ExecuteUpdate();
307
                            return updatedEntities;
308
                        }, null);
309

    
310
        }
311
*/
312

    
313
/*
314
        public static void UpdateStatus(string absolutePath, FileStatus fileStatus)
315
        {
316
            if (string.IsNullOrWhiteSpace(absolutePath))
317
                throw new ArgumentNullException("absolutePath");
318
            Contract.EndContractBlock();
319

    
320
            ExecuteWithRetry((session, instance) =>
321
                        {
322
                            const string hqlUpdate =
323
                                "update FileState set FileStatus= :fileStatus where FilePath = :path  ";
324
                            var updatedEntities = session.CreateQuery(hqlUpdate)
325
                                .SetString("path", absolutePath)
326
                                .SetEnum("fileStatus", fileStatus)
327
                                .ExecuteUpdate();
328
                            return updatedEntities;
329
                        }, null);
330

    
331
        }
332

    
333
*/
334
        public static void RenameState(string oldPath, string newPath)
335
        {
336
            if (string.IsNullOrWhiteSpace(oldPath))
337
                throw new ArgumentNullException("oldPath");
338
            Contract.EndContractBlock();
339

    
340
            ExecuteWithRetry((session, instance) =>
341
                        {
342
                            const string hqlUpdate =
343
                                "update FileState set FilePath= :newPath where FilePath = :oldPath ";
344
                            var updatedEntities = session.CreateQuery(hqlUpdate)
345
                                .SetString("oldPath", oldPath)
346
                                .SetString("newPath", newPath)
347
                                .ExecuteUpdate();
348
                            return updatedEntities;
349
                        }, null);
350

    
351
        }
352

    
353
     /*   public static void UpdateStatus(Guid id, FileStatus fileStatus)
354
        {
355

    
356
            ExecuteWithRetry((session, instance) =>
357
            {
358
                const string hqlUpdate =
359
                    "update FileState set FileStatus= :fileStatus where Id = :id  ";
360
                var updatedEntities = session.CreateQuery(hqlUpdate)
361
                    .SetGuid("id", id)
362
                    .SetEnum("fileStatus", fileStatus)
363
                    .ExecuteUpdate();
364
                return updatedEntities;
365
            }, null);
366
        }*/
367

    
368
        public static void UpdateFileTreeHash(string absolutePath, TreeHash treeHash)
369
        {
370
            if (string.IsNullOrWhiteSpace(absolutePath))
371
                throw new ArgumentNullException("absolutePath");
372
            Contract.EndContractBlock();
373

    
374
            ExecuteWithRetry((session, instance) =>
375
            {
376
                
377
                var hashes=treeHash.ToJson();
378
                const string hqlUpdate = "update FileState set Checksum= :checksum,ETag=:etag, Hashes=:hashes where FilePath = :path ";
379
                var updatedEntities = session.CreateQuery(hqlUpdate)
380
                    .SetString("path", absolutePath)
381
                    .SetString("checksum", treeHash.TopHash.ToHashString())
382
                    .SetString("etag", treeHash.MD5)
383
                    .SetString("hashes",hashes)
384
                    .ExecuteUpdate();
385
                return updatedEntities;
386
            }, null);
387
        }
388

    
389
        public static void UpdateChecksum(string absolutePath, string etag, string checksum)
390
        {
391
            if (string.IsNullOrWhiteSpace(absolutePath))
392
                throw new ArgumentNullException("absolutePath");
393
            Contract.EndContractBlock();
394

    
395
            ExecuteWithRetry((session, instance) =>
396
                        {
397
                            const string hqlUpdate = "update FileState set Checksum= :checksum,ETag=:etag where FilePath = :path ";
398
                            var updatedEntities = session.CreateQuery(hqlUpdate)
399
                                .SetString("path", absolutePath)
400
                                .SetString("checksum", checksum)
401
                                .SetString("etag", etag)
402
                                .ExecuteUpdate();
403
                            return updatedEntities;
404
                        }, null);
405

    
406
        }
407

    
408

    
409
        public static void UpdateLastMD5(FileInfo file, string md5)
410
        {
411
            if (file==null)
412
                throw new ArgumentNullException("file");
413
            Contract.EndContractBlock();
414

    
415
            ExecuteWithRetry((session, instance) =>
416
                        {
417
                            const string hqlUpdate = "update FileState set LastMD5=:md5, LastWriteDate=:date,LastLength=:length where FilePath = :path ";
418
                            
419
                            file.Refresh();
420
                            if (!file.Exists)
421
                                return 0;
422
                            var fullName = file.WithProperCapitalization().FullName;
423

    
424
                            using (var tx=session.BeginTransaction())
425
                            {
426
                                var updatedEntities = session.CreateQuery(hqlUpdate)
427
                                    .SetDateTime("date", file.LastWriteTime)
428
                                    .SetInt64("length", file.Length)
429
                                    .SetString("md5", md5)
430
                                    .SetString("path", fullName)
431
                                    .ExecuteUpdate();
432
                                if (updatedEntities == 0)
433
                                {
434
                                    var newState = new FileState
435
                                                       {
436
                                                           FilePath = fullName,
437
                                                           Id = Guid.NewGuid(),
438
                                                           OverlayStatus = FileOverlayStatus.Normal,
439
                                                           FileStatus = FileStatus.Unchanged,
440
                                                           IsFolder = false,
441
                                                           LastLength = file.Length,
442
                                                           LastWriteDate = file.LastWriteTime,
443
                                                           LastMD5 = md5 ?? String.Empty,
444
                                                           ETag = String.Empty
445
                                                       };
446
                                    newState.CreateAndFlush();
447
                                }
448
                                tx.Commit();
449
                                return updatedEntities;
450
                            }
451
                        }, null);
452

    
453
        }
454

    
455
        public static void ChangeRootPath(string oldPath, string newPath)
456
        {
457
            if (String.IsNullOrWhiteSpace(oldPath))
458
                throw new ArgumentNullException("oldPath");
459
            if (!Path.IsPathRooted(oldPath))
460
                throw new ArgumentException("oldPath must be an absolute path", "oldPath");
461
            if (string.IsNullOrWhiteSpace(newPath))
462
                throw new ArgumentNullException("newPath");
463
            if (!Path.IsPathRooted(newPath))
464
                throw new ArgumentException("newPath must be an absolute path", "newPath");
465
            Contract.EndContractBlock();
466

    
467
            //Ensure the paths end with the same character
468
            if (!oldPath.EndsWith("\\"))
469
                oldPath = oldPath + "\\";
470
            if (!newPath.EndsWith("\\"))
471
                newPath = newPath + "\\";
472

    
473
                ExecuteWithRetry((session, instance) =>
474
                            {
475
                                const string hqlUpdate =
476
                                    "update FileState set FilePath = replace(FilePath,:oldPath,:newPath) where FilePath like :oldPath || '%' ";
477
                                var renames = session.CreateQuery(hqlUpdate)
478
                                    .SetString("oldPath", oldPath)
479
                                    .SetString("newPath", newPath)
480
                                    .ExecuteUpdate();
481
                                return renames;
482
                            }, null);
483
        }
484

    
485
        public static FileState CreateFor(FileSystemInfo info,IStatusNotification notification)
486
        {
487
            if(info==null)
488
                throw new ArgumentNullException("info");
489
            Contract.EndContractBlock();
490
            
491
            if (info is DirectoryInfo)
492
                return new FileState
493
                {
494
                    FilePath = info.FullName,
495
                    OverlayStatus = FileOverlayStatus.Unversioned,
496
                    FileStatus = FileStatus.Created,
497
                    ETag=String.Empty,
498
                    LastMD5=String.Empty,
499
                    Id = Guid.NewGuid()
500
                };
501

    
502
            
503
            var etag = ((FileInfo)info).ComputeShortHash(notification);
504
            var fileState = new FileState
505
                                {
506
                                    FilePath = info.FullName,
507
                                    OverlayStatus = FileOverlayStatus.Unversioned,
508
                                    FileStatus = FileStatus.Created,               
509
                                    ETag=etag,
510
                                    LastMD5=String.Empty,
511
                                    Id = Guid.NewGuid()
512
                                };
513
            return fileState;
514
        }
515

    
516

    
517
        private static void ExecuteWithRetry(NHibernateDelegate call, object state)
518
        {
519
            int retries = 3;
520
            while (retries > 0)
521
                try
522
                {
523
                    using (new SessionScope())
524
                    {
525
                        Execute(call, state);
526
                        return;
527
                    }
528
                }
529
                catch (ActiveRecordException )
530
                {
531
                    retries--;
532
                    if (retries <= 0)
533
                        throw;
534
                }
535
        }
536

    
537
        /// <summary>
538
        /// Mark Unversioned all FileState rows from the database whose path
539
        /// starts with one of the removed paths
540
        /// </summary>
541
        /// <param name="removed"></param>
542
        public static void UnversionPaths(List<string> removed)
543
        {
544
            if (removed == null)
545
                return;
546
            if (removed.Count == 0)
547
                return;
548

    
549
            //Create a disjunction (list of OR statements
550
            var disjunction = new Disjunction();            
551
            foreach (var path in removed)
552
            {
553
                //with the restriction FileState.FilePath like '@path%'
554
                disjunction.Add(Restrictions.On<FileState>(s => s.FilePath)
555
                    .IsLike(path, MatchMode.Start));
556
            }
557

    
558
            //Generate a query from the disjunction
559
            var query=QueryOver.Of<FileState>().Where(disjunction);
560
                        
561
            ExecuteWithRetry((session,instance)=>
562
                                 {
563
                                     using (var tx=session.BeginTransaction())
564
                                     {
565
                                         var states = query.GetExecutableQueryOver(session).List();
566
                                         foreach (var state in states)
567
                                         {
568
                                             state.FileStatus = FileStatus.Unversioned;
569
                                             state.OverlayStatus = FileOverlayStatus.Unversioned;
570
                                             state.Update();
571
                                         }
572
                                         tx.Commit();
573
                                     }
574
                                     return null;
575
                                 },null);
576
        }
577

    
578
        public string ToDebugString()
579
        {
580
            return String.Format("[STATE] FilePath:[{0}] ObjectID:[{1}], ETAG: [{2}], LastMD5:[{3}]", FilePath, ObjectID,
581
                                 ETag, LastMD5);
582
        }
583
    }
584

    
585
    [ActiveRecord("Tags")]
586
    public class FileTag : ActiveRecordLinqBase<FileTag>
587
    {
588
        [PrimaryKey]
589
        public int Id { get; set; }
590

    
591
        [Property]
592
        public string Name { get; set; }
593

    
594
        [Property]
595
        public string Value { get; set; }
596

    
597
        [BelongsTo("FileStateId")]
598
        public FileState FileState { get; set; }
599

    
600
    }
601
   
602
}