Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / FileState.cs @ 159098f7

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
        [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
        private string _shortHash;
114

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

    
132

    
133
        [Property]
134
        public long? Version { get; set; }
135

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

    
139

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

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

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

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

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

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

    
162

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

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

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

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

    
191
                
192

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

    
201

    
202
        }*/
203

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

    
210
            ExecuteWithRetry((session, instance) =>
211
                        {
212
                            const string hqlDelete = "delete FileState where FilePath = :path";
213
                            var deletedEntities = session.CreateQuery(hqlDelete)
214
                                .SetString("path", absolutePath)
215
                                .ExecuteUpdate();
216
                            return deletedEntities;
217
                        }, null);
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 = "update FileState set FileStatus= :status where FilePath = :path ";
230
                            var updatedEntities = session.CreateQuery(hqlUpdate)
231
                                .SetString("path", absolutePath)
232
                                .SetEnum("status", newStatus)
233
                                .ExecuteUpdate();
234
                            if (updatedEntities == 0)
235
                            {
236
                                var newState = new FileState
237
                                                   {
238
                                                       FilePath = absolutePath,
239
                                                       Id = Guid.NewGuid(),
240
                                                       FileStatus = newStatus,
241
                                                       IsFolder=Directory.Exists(absolutePath)
242
                                                   };
243
                                newState.CreateAndFlush();
244
                            }
245
                            return null;
246
                        }, null);
247

    
248
        }
249

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

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

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

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

    
310
        }
311

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

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

    
331
        }
332
*/
333

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

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

    
352
        }
353

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

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

    
372
        }
373

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

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

    
389
        public static void UpdateChecksum(string absolutePath, string shortHash, 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,ShortHash=:shortHash where FilePath = :path ";
398
                            var updatedEntities = session.CreateQuery(hqlUpdate)
399
                                .SetString("path", absolutePath)
400
                                .SetString("checksum", checksum)
401
                                .SetString("shortHash", shortHash)
402
                                .ExecuteUpdate();
403
                            return updatedEntities;
404
                        }, null);
405

    
406
        }
407

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

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

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

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

    
454

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

    
467

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

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

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

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

    
529
    }
530

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

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

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

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

    
546
    }
547
   
548
}