Statistics
| Branch: | Revision:

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

History | View | Annotate | Download (20.6 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_FilePath")]
76
        public string FilePath { get; set; }
77

    
78
        [Property]
79
        public FileOverlayStatus OverlayStatus { get; set; }
80

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

    
84
        private string _checksum;
85

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

    
106
        private string _shortHash;
107

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

    
125

    
126
        [Property]
127
        public long? Version { get; set; }
128

    
129
        [Property]
130
        public DateTime? VersionTimeStamp { get; set; }
131

    
132

    
133
        [Property]
134
        public bool IsShared { get; set; }
135

    
136
        [Property]
137
        public string SharedBy { get; set; }
138

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

    
142
        [Property]
143
        public bool IsFolder{ get; set; }
144

    
145
        [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true, Inverse = true)]
146
        public IList<FileTag> Tags
147
        {
148
            get { return _tags; }
149
            set { _tags = value; }
150
        }
151

    
152
        [Property]
153
        public DateTime Modified { get; set; }
154

    
155

    
156
        public FileSystemInfo GetFileSystemInfo()
157
        {
158
            if (String.IsNullOrWhiteSpace(FilePath))
159
                throw new InvalidOperationException();
160
            Contract.EndContractBlock();
161

    
162
            return Directory.Exists(FilePath) ?
163
                (FileSystemInfo)new DirectoryInfo(FilePath)
164
                : new FileInfo(FilePath);
165
        }
166

    
167
        public string GetRelativeUrl(AccountInfo accountInfo)
168
        {
169
            if (accountInfo==null)
170
                throw new ArgumentNullException("accountInfo");
171
            Contract.EndContractBlock();
172

    
173
            var fsi=GetFileSystemInfo();
174
            return fsi.AsRelativeUrlTo(accountInfo.AccountPath);
175
        }
176
        /*public static FileState FindByFilePath(string absolutePath)
177
        {
178
            if (string.IsNullOrWhiteSpace(absolutePath))
179
                throw new ArgumentNullException("absolutePath");
180
            Contract.EndContractBlock();
181
            try
182
            {
183

    
184
                
185

    
186
                return Queryable.FirstOrDefault(s => s.FilePath == absolutePath);
187
            }
188
            catch (Exception ex)
189
            {
190
                Log.Error(ex.ToString());
191
                throw;
192
            }
193

    
194

    
195
        }*/
196

    
197
       /* public static void DeleteByFilePath(string absolutePath)
198
        {
199
            if (string.IsNullOrWhiteSpace(absolutePath))
200
                throw new ArgumentNullException("absolutePath");
201
            Contract.EndContractBlock();
202

    
203
            ExecuteWithRetry((session, instance) =>
204
                        {
205
                            const string hqlDelete = "delete FileState where FilePath = :path";
206
                            var deletedEntities = session.CreateQuery(hqlDelete)
207
                                .SetString("path", absolutePath)
208
                                .ExecuteUpdate();
209
                            return deletedEntities;
210
                        }, null);
211

    
212
        }*/
213

    
214
        public static void StoreFileStatus(string absolutePath, FileStatus newStatus)
215
        {
216
            if (string.IsNullOrWhiteSpace(absolutePath))
217
                throw new ArgumentNullException("absolutePath");
218
            Contract.EndContractBlock();
219

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

    
241
        }
242

    
243
        /*public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus)
244
        {
245
            if (string.IsNullOrWhiteSpace(absolutePath))
246
                throw new ArgumentNullException("absolutePath");
247
            Contract.EndContractBlock();
248

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

    
272
        }
273
*/
274
        public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus,string shortHash)
275
        {
276
            if (string.IsNullOrWhiteSpace(absolutePath))
277
                throw new ArgumentNullException("absolutePath");
278
            Contract.EndContractBlock();
279

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

    
303
        }
304

    
305
/*
306
        public static void UpdateStatus(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
307
        {
308
            if (string.IsNullOrWhiteSpace(absolutePath))
309
                throw new ArgumentNullException("absolutePath");
310
            Contract.EndContractBlock();
311

    
312
            ExecuteWithRetry((session, instance) =>
313
                        {
314
                            const string hqlUpdate =
315
                                "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path  ";
316
                            var updatedEntities = session.CreateQuery(hqlUpdate)
317
                                .SetString("path", absolutePath)
318
                                .SetEnum("fileStatus", fileStatus)
319
                                .SetEnum("overlayStatus", overlayStatus)
320
                                .ExecuteUpdate();
321
                            return updatedEntities;
322
                        }, null);
323

    
324
        }
325
*/
326

    
327
/*
328
        public static void UpdateStatus(string absolutePath, FileStatus fileStatus)
329
        {
330
            if (string.IsNullOrWhiteSpace(absolutePath))
331
                throw new ArgumentNullException("absolutePath");
332
            Contract.EndContractBlock();
333

    
334
            ExecuteWithRetry((session, instance) =>
335
                        {
336
                            const string hqlUpdate =
337
                                "update FileState set FileStatus= :fileStatus where FilePath = :path  ";
338
                            var updatedEntities = session.CreateQuery(hqlUpdate)
339
                                .SetString("path", absolutePath)
340
                                .SetEnum("fileStatus", fileStatus)
341
                                .ExecuteUpdate();
342
                            return updatedEntities;
343
                        }, null);
344

    
345
        }
346

    
347
*/
348
        public static void RenameState(string oldPath, string newPath)
349
        {
350
            if (string.IsNullOrWhiteSpace(oldPath))
351
                throw new ArgumentNullException("oldPath");
352
            Contract.EndContractBlock();
353

    
354
            ExecuteWithRetry((session, instance) =>
355
                        {
356
                            const string hqlUpdate =
357
                                "update FileState set FilePath= :newPath where FilePath = :oldPath ";
358
                            var updatedEntities = session.CreateQuery(hqlUpdate)
359
                                .SetString("oldPath", oldPath)
360
                                .SetString("newPath", newPath)
361
                                .ExecuteUpdate();
362
                            return updatedEntities;
363
                        }, null);
364

    
365
        }
366

    
367
     /*   public static void UpdateStatus(Guid id, FileStatus fileStatus)
368
        {
369

    
370
            ExecuteWithRetry((session, instance) =>
371
            {
372
                const string hqlUpdate =
373
                    "update FileState set FileStatus= :fileStatus where Id = :id  ";
374
                var updatedEntities = session.CreateQuery(hqlUpdate)
375
                    .SetGuid("id", id)
376
                    .SetEnum("fileStatus", fileStatus)
377
                    .ExecuteUpdate();
378
                return updatedEntities;
379
            }, null);
380
        }*/
381

    
382
        public static void UpdateChecksum(string absolutePath, string shortHash, string checksum)
383
        {
384
            if (string.IsNullOrWhiteSpace(absolutePath))
385
                throw new ArgumentNullException("absolutePath");
386
            Contract.EndContractBlock();
387

    
388
            ExecuteWithRetry((session, instance) =>
389
                        {
390
                            const string hqlUpdate = "update FileState set Checksum= :checksum,ShortHash=:shortHash where FilePath = :path ";
391
                            var updatedEntities = session.CreateQuery(hqlUpdate)
392
                                .SetString("path", absolutePath)
393
                                .SetString("checksum", checksum)
394
                                .SetString("shortHash", shortHash)
395
                                .ExecuteUpdate();
396
                            return updatedEntities;
397
                        }, null);
398

    
399
        }
400

    
401
        public static void ChangeRootPath(string oldPath, string newPath)
402
        {
403
            if (String.IsNullOrWhiteSpace(oldPath))
404
                throw new ArgumentNullException("oldPath");
405
            if (!Path.IsPathRooted(oldPath))
406
                throw new ArgumentException("oldPath must be an absolute path", "oldPath");
407
            if (string.IsNullOrWhiteSpace(newPath))
408
                throw new ArgumentNullException("newPath");
409
            if (!Path.IsPathRooted(newPath))
410
                throw new ArgumentException("newPath must be an absolute path", "newPath");
411
            Contract.EndContractBlock();
412

    
413
            //Ensure the paths end with the same character
414
            if (!oldPath.EndsWith("\\"))
415
                oldPath = oldPath + "\\";
416
            if (!newPath.EndsWith("\\"))
417
                newPath = newPath + "\\";
418

    
419
                ExecuteWithRetry((session, instance) =>
420
                            {
421
                                const string hqlUpdate =
422
                                    "update FileState set FilePath = replace(FilePath,:oldPath,:newPath) where FilePath like :oldPath || '%' ";
423
                                var renames = session.CreateQuery(hqlUpdate)
424
                                    .SetString("oldPath", oldPath)
425
                                    .SetString("newPath", newPath)
426
                                    .ExecuteUpdate();
427
                                return renames;
428
                            }, null);
429
        }
430

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

    
447

    
448
            var shortHash = ((FileInfo)info).ComputeShortHash();
449
            var fileState = new FileState
450
                                {
451
                                    FilePath = info.FullName,
452
                                    OverlayStatus = FileOverlayStatus.Unversioned,
453
                                    FileStatus = FileStatus.Created,               
454
                                    ShortHash=shortHash,
455
                                    Id = Guid.NewGuid()
456
                                };
457
            return fileState;
458
        }
459

    
460

    
461
        private static void ExecuteWithRetry(NHibernateDelegate call, object state)
462
        {
463
            int retries = 3;
464
            while (retries > 0)
465
                try
466
                {
467
                    using (new SessionScope())
468
                    {
469
                        Execute(call, state);
470
                        return;
471
                    }
472
                }
473
                catch (ActiveRecordException )
474
                {
475
                    retries--;
476
                    if (retries <= 0)
477
                        throw;
478
                }
479
        }
480

    
481
        /// <summary>
482
        /// Mark Unversioned all FileState rows from the database whose path
483
        /// starts with one of the removed paths
484
        /// </summary>
485
        /// <param name="removed"></param>
486
        public static void UnversionPaths(List<string> removed)
487
        {
488
            if (removed == null)
489
                return;
490
            if (removed.Count == 0)
491
                return;
492

    
493
            //Create a disjunction (list of OR statements
494
            var disjunction = new Disjunction();            
495
            foreach (var path in removed)
496
            {
497
                //with the restriction FileState.FilePath like '@path%'
498
                disjunction.Add(Restrictions.On<FileState>(s => s.FilePath)
499
                    .IsLike(path, MatchMode.Start));
500
            }
501

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

    
522
    }
523

    
524
    [ActiveRecord("Tags")]
525
    public class FileTag : ActiveRecordLinqBase<FileTag>
526
    {
527
        [PrimaryKey]
528
        public int Id { get; set; }
529

    
530
        [Property]
531
        public string Name { get; set; }
532

    
533
        [Property]
534
        public string Value { get; set; }
535

    
536
        [BelongsTo("FileStateId")]
537
        public FileState FileState { get; set; }
538

    
539
    }
540
   
541
}