Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / FileState.cs @ c945b450

History | View | Annotate | Download (20.8 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
        public string ObjectID { get; set; }
77

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

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

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

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

    
90
        private string _checksum;
91

    
92
        /// <summary>
93
        /// The tophash value of the file, calculated by using a Merkle hash with the SHA256 algorithm
94
        /// </summary>
95
        /// <remarks>
96
        /// The SHA256 algorithm is substantially more expenive than other algorithms.
97
        /// Recalculating the Checksum should be avoided whenever possible.
98
        /// </remarks>
99
        [Property]
100
        public string Checksum
101
        {
102
            get
103
            {
104
                return _checksum;
105
            }
106
            set
107
            {
108
                _checksum = value;
109
            }
110
        }
111

    
112
        private string _shortHash;
113

    
114
        /// <summary>
115
        /// An easy to calcualte hash over the entire file, used to detect file changes
116
        /// </summary>
117
        /// <remarks>The algorithm used to calculate this hash should be cheap</remarks>
118
        [Property(NotNull=true,Default="")]
119
        public string ShortHash
120
        {
121
            get
122
            {
123
                return _shortHash;
124
            }
125
            set
126
            {
127
                _shortHash = value;
128
            }
129
        }
130

    
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
        public static void StoreFileStatus(string absolutePath, FileStatus newStatus)
221
        {
222
            if (string.IsNullOrWhiteSpace(absolutePath))
223
                throw new ArgumentNullException("absolutePath");
224
            Contract.EndContractBlock();
225

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

    
247
        }
248

    
249
        /*public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus)
250
        {
251
            if (string.IsNullOrWhiteSpace(absolutePath))
252
                throw new ArgumentNullException("absolutePath");
253
            Contract.EndContractBlock();
254

    
255
            ExecuteWithRetry((session, instance) =>
256
                        {
257
                            const string hqlUpdate =
258
                                "update FileState set OverlayStatus= :status where FilePath = :path ";
259
                            var updatedEntities = session.CreateQuery(hqlUpdate)                                
260
                                .SetString("path", absolutePath)
261
                                .SetEnum("status", newStatus)                                
262
                                .ExecuteUpdate();
263
                            if (updatedEntities == 0)
264
                            {
265
                                var newState = new FileState
266
                                                   {
267
                                                       FilePath = absolutePath,
268
                                                       Id = Guid.NewGuid(),
269
                                                       OverlayStatus = newStatus,
270
                                                       ShortHash = String.Empty,
271
                                                       IsFolder=Directory.Exists(absolutePath)
272
                                                   };
273
                                newState.CreateAndFlush();
274
                            }
275
                            return null;
276
                        }, null);
277

    
278
        }
279
*/
280
        public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus,string shortHash)
281
        {
282
            if (string.IsNullOrWhiteSpace(absolutePath))
283
                throw new ArgumentNullException("absolutePath");
284
            Contract.EndContractBlock();
285

    
286
            ExecuteWithRetry((session, instance) =>
287
                        {
288
                            const string hqlUpdate =
289
                                "update FileState set OverlayStatus= :status where FilePath = :path ";
290
                            var updatedEntities = session.CreateQuery(hqlUpdate)                                
291
                                .SetString("path", absolutePath)
292
                                .SetEnum("status", newStatus)                                
293
                                .ExecuteUpdate();
294
                            if (updatedEntities == 0)
295
                            {
296
                                var newState = new FileState
297
                                                   {
298
                                                       FilePath = absolutePath,
299
                                                       Id = Guid.NewGuid(),
300
                                                       OverlayStatus = newStatus,
301
                                                       ShortHash = shortHash??String.Empty,
302
                                                       IsFolder=Directory.Exists(absolutePath)
303
                                                   };
304
                                newState.CreateAndFlush();
305
                            }
306
                            return null;
307
                        }, null);
308

    
309
        }
310

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

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

    
330
        }
331
*/
332

    
333
/*
334
        public static void UpdateStatus(string absolutePath, FileStatus fileStatus)
335
        {
336
            if (string.IsNullOrWhiteSpace(absolutePath))
337
                throw new ArgumentNullException("absolutePath");
338
            Contract.EndContractBlock();
339

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

    
351
        }
352

    
353
*/
354
        public static void RenameState(string oldPath, string newPath)
355
        {
356
            if (string.IsNullOrWhiteSpace(oldPath))
357
                throw new ArgumentNullException("oldPath");
358
            Contract.EndContractBlock();
359

    
360
            ExecuteWithRetry((session, instance) =>
361
                        {
362
                            const string hqlUpdate =
363
                                "update FileState set FilePath= :newPath where FilePath = :oldPath ";
364
                            var updatedEntities = session.CreateQuery(hqlUpdate)
365
                                .SetString("oldPath", oldPath)
366
                                .SetString("newPath", newPath)
367
                                .ExecuteUpdate();
368
                            return updatedEntities;
369
                        }, null);
370

    
371
        }
372

    
373
     /*   public static void UpdateStatus(Guid id, FileStatus fileStatus)
374
        {
375

    
376
            ExecuteWithRetry((session, instance) =>
377
            {
378
                const string hqlUpdate =
379
                    "update FileState set FileStatus= :fileStatus where Id = :id  ";
380
                var updatedEntities = session.CreateQuery(hqlUpdate)
381
                    .SetGuid("id", id)
382
                    .SetEnum("fileStatus", fileStatus)
383
                    .ExecuteUpdate();
384
                return updatedEntities;
385
            }, null);
386
        }*/
387

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

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

    
405
        }
406

    
407
        public static void ChangeRootPath(string oldPath, string newPath)
408
        {
409
            if (String.IsNullOrWhiteSpace(oldPath))
410
                throw new ArgumentNullException("oldPath");
411
            if (!Path.IsPathRooted(oldPath))
412
                throw new ArgumentException("oldPath must be an absolute path", "oldPath");
413
            if (string.IsNullOrWhiteSpace(newPath))
414
                throw new ArgumentNullException("newPath");
415
            if (!Path.IsPathRooted(newPath))
416
                throw new ArgumentException("newPath must be an absolute path", "newPath");
417
            Contract.EndContractBlock();
418

    
419
            //Ensure the paths end with the same character
420
            if (!oldPath.EndsWith("\\"))
421
                oldPath = oldPath + "\\";
422
            if (!newPath.EndsWith("\\"))
423
                newPath = newPath + "\\";
424

    
425
                ExecuteWithRetry((session, instance) =>
426
                            {
427
                                const string hqlUpdate =
428
                                    "update FileState set FilePath = replace(FilePath,:oldPath,:newPath) where FilePath like :oldPath || '%' ";
429
                                var renames = session.CreateQuery(hqlUpdate)
430
                                    .SetString("oldPath", oldPath)
431
                                    .SetString("newPath", newPath)
432
                                    .ExecuteUpdate();
433
                                return renames;
434
                            }, null);
435
        }
436

    
437
        public static FileState CreateFor(FileSystemInfo info)
438
        {
439
            if(info==null)
440
                throw new ArgumentNullException("info");
441
            Contract.EndContractBlock();
442
            
443
            if (info is DirectoryInfo)
444
                return new FileState
445
                {
446
                    FilePath = info.FullName,
447
                    OverlayStatus = FileOverlayStatus.Unversioned,
448
                    FileStatus = FileStatus.Created,
449
                    ShortHash=String.Empty,
450
                    Id = Guid.NewGuid()
451
                };
452

    
453

    
454
            var shortHash = ((FileInfo)info).ComputeShortHash();
455
            var fileState = new FileState
456
                                {
457
                                    FilePath = info.FullName,
458
                                    OverlayStatus = FileOverlayStatus.Unversioned,
459
                                    FileStatus = FileStatus.Created,               
460
                                    ShortHash=shortHash,
461
                                    Id = Guid.NewGuid()
462
                                };
463
            return fileState;
464
        }
465

    
466

    
467
        private static void ExecuteWithRetry(NHibernateDelegate call, object state)
468
        {
469
            int retries = 3;
470
            while (retries > 0)
471
                try
472
                {
473
                    using (new SessionScope())
474
                    {
475
                        Execute(call, state);
476
                        return;
477
                    }
478
                }
479
                catch (ActiveRecordException )
480
                {
481
                    retries--;
482
                    if (retries <= 0)
483
                        throw;
484
                }
485
        }
486

    
487
        /// <summary>
488
        /// Mark Unversioned all FileState rows from the database whose path
489
        /// starts with one of the removed paths
490
        /// </summary>
491
        /// <param name="removed"></param>
492
        public static void UnversionPaths(List<string> removed)
493
        {
494
            if (removed == null)
495
                return;
496
            if (removed.Count == 0)
497
                return;
498

    
499
            //Create a disjunction (list of OR statements
500
            var disjunction = new Disjunction();            
501
            foreach (var path in removed)
502
            {
503
                //with the restriction FileState.FilePath like '@path%'
504
                disjunction.Add(Restrictions.On<FileState>(s => s.FilePath)
505
                    .IsLike(path, MatchMode.Start));
506
            }
507

    
508
            //Generate a query from the disjunction
509
            var query=QueryOver.Of<FileState>().Where(disjunction);
510
                        
511
            ExecuteWithRetry((session,instance)=>
512
                                 {
513
                                     using (var t=session.BeginTransaction())
514
                                     {
515
                                         var states = query.GetExecutableQueryOver(session).List();
516
                                         foreach (var state in states)
517
                                         {
518
                                             state.FileStatus = FileStatus.Unversioned;
519
                                             state.OverlayStatus = FileOverlayStatus.Unversioned;
520
                                             state.Update();
521
                                         }
522
                                         t.Commit();
523
                                     }
524
                                     return null;
525
                                 },null);
526
        }
527

    
528
    }
529

    
530
    [ActiveRecord("Tags")]
531
    public class FileTag : ActiveRecordLinqBase<FileTag>
532
    {
533
        [PrimaryKey]
534
        public int Id { get; set; }
535

    
536
        [Property]
537
        public string Name { get; set; }
538

    
539
        [Property]
540
        public string Value { get; set; }
541

    
542
        [BelongsTo("FileStateId")]
543
        public FileState FileState { get; set; }
544

    
545
    }
546
   
547
}