Convert ActiveRecord update code to direct ADO calls to reduce locks
[pithos-ms-client] / trunk / Pithos.Core / FileState.cs
1 // -----------------------------------------------------------------------
2 // <copyright file="FileState.cs" company="GRNet">
3 // Copyright 2011 GRNET S.A. All rights reserved.
4 // 
5 // Redistribution and use in source and binary forms, with or
6 // without modification, are permitted provided that the following
7 // conditions are met:
8 // 
9 //   1. Redistributions of source code must retain the above
10 //      copyright notice, this list of conditions and the following
11 //      disclaimer.
12 // 
13 //   2. Redistributions in binary form must reproduce the above
14 //      copyright notice, this list of conditions and the following
15 //      disclaimer in the documentation and/or other materials
16 //      provided with the distribution.
17 // 
18 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 // POSSIBILITY OF SUCH DAMAGE.
30 // 
31 // The views and conclusions contained in the software and
32 // documentation are those of the authors and should not be
33 // interpreted as representing official policies, either expressed
34 // or implied, of GRNET S.A.
35 // </copyright>
36 // -----------------------------------------------------------------------
37
38 using System.Data.SQLite;
39 using System.Diagnostics.Contracts;
40 using System.IO;
41 using System.Threading.Tasks;
42 using Castle.ActiveRecord;
43 using Castle.ActiveRecord.Framework;
44 using Pithos.Core.Agents;
45 using Pithos.Interfaces;
46 using log4net;
47
48 namespace Pithos.Core
49 {
50     using System;
51     using System.Collections.Generic;
52     using System.Linq;
53
54     /// <summary>
55     /// TODO: Update summary.
56     /// </summary>
57     [ActiveRecord]
58     public class FileState : ActiveRecordLinqBase<FileState>
59     {
60         private static readonly ILog Log = LogManager.GetLogger("FileState");
61
62         private string _filePath;
63         private IList<FileTag> _tags = new List<FileTag>();
64
65         [PrimaryKey(PrimaryKeyType.Guid)]
66         public Guid Id { get; set; }
67
68         [Property(Unique = true, UniqueKey = "IX_FileState_FilePath")]
69         public string FilePath
70         {
71             get { return _filePath; }
72             set { _filePath = value.ToLower(); }
73         }
74
75         [Property]
76         public FileOverlayStatus OverlayStatus { get; set; }
77
78         [Property]
79         public FileStatus FileStatus { get; set; }
80
81         [Property]
82         public string Checksum { get; set; }
83
84
85         [Property]
86         public long? Version { get; set; }
87
88         [Property]
89         public DateTime? VersionTimeStamp { get; set; }
90
91
92         [Property]
93         public bool IsShared { get; set; }
94
95         [Property]
96         public string SharedBy { get; set; }
97
98         [Property]
99         public bool ShareWrite { get; set; }
100
101
102         [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true, Inverse = true)]
103         public IList<FileTag> Tags
104         {
105             get { return _tags; }
106             set { _tags = value; }
107         }
108
109
110         public static FileState FindByFilePath(string absolutePath)
111         {
112             if (string.IsNullOrWhiteSpace(absolutePath))
113                 throw new ArgumentNullException("absolutePath");
114             Contract.EndContractBlock();
115             try
116             {
117
118                 
119
120                 return Queryable.FirstOrDefault(s => s.FilePath == absolutePath.ToLower());
121             }
122             catch (Exception ex)
123             {
124                 Log.Error(ex.ToString());
125                 throw;
126             }
127
128
129         }
130
131        /* public static void DeleteByFilePath(string absolutePath)
132         {
133             if (string.IsNullOrWhiteSpace(absolutePath))
134                 throw new ArgumentNullException("absolutePath");
135             Contract.EndContractBlock();
136
137             ExecuteWithRetry((session, instance) =>
138                         {
139                             const string hqlDelete = "delete FileState where FilePath = :path";
140                             var deletedEntities = session.CreateQuery(hqlDelete)
141                                 .SetString("path", absolutePath.ToLower())
142                                 .ExecuteUpdate();
143                             return deletedEntities;
144                         }, null);
145
146         }*/
147
148         public static void StoreFileStatus(string absolutePath, FileStatus newStatus)
149         {
150             if (string.IsNullOrWhiteSpace(absolutePath))
151                 throw new ArgumentNullException("absolutePath");
152             Contract.EndContractBlock();
153
154             ExecuteWithRetry((session, instance) =>
155                         {
156                             const string hqlUpdate = "update FileState set FileStatus= :status where FilePath = :path  ";
157                             var updatedEntities = session.CreateQuery(hqlUpdate)
158                                 .SetString("path", absolutePath.ToLower())
159                                 .SetEnum("status", newStatus)
160                                 .ExecuteUpdate();
161                             if (updatedEntities == 0)
162                             {
163                                 var newState = new FileState
164                                                    {
165                                                        FilePath = absolutePath.ToLower(),
166                                                        Id = Guid.NewGuid(),
167                                                        FileStatus = newStatus
168                                                    };
169                                 newState.CreateAndFlush();
170                             }
171                             return null;
172                         }, null);
173
174         }
175
176         public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus)
177         {
178             if (string.IsNullOrWhiteSpace(absolutePath))
179                 throw new ArgumentNullException("absolutePath");
180             Contract.EndContractBlock();
181
182             ExecuteWithRetry((session, instance) =>
183                         {
184                             const string hqlUpdate =
185                                 "update FileState set OverlayStatus= :status where FilePath = :path  ";
186                             var updatedEntities = session.CreateQuery(hqlUpdate)
187                                 .SetString("path", absolutePath.ToLower())
188                                 .SetEnum("status", newStatus)
189                                 .ExecuteUpdate();
190                             if (updatedEntities == 0)
191                             {
192                                 var newState = new FileState
193                                                    {
194                                                        FilePath = absolutePath,
195                                                        Id = Guid.NewGuid(),
196                                                        OverlayStatus = newStatus
197                                                    };
198                                 newState.CreateAndFlush();
199                             }
200                             return null;
201                         }, null);
202
203         }
204
205 /*
206         public static void UpdateStatus(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
207         {
208             if (string.IsNullOrWhiteSpace(absolutePath))
209                 throw new ArgumentNullException("absolutePath");
210             Contract.EndContractBlock();
211
212             ExecuteWithRetry((session, instance) =>
213                         {
214                             const string hqlUpdate =
215                                 "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path  ";
216                             var updatedEntities = session.CreateQuery(hqlUpdate)
217                                 .SetString("path", absolutePath.ToLower())
218                                 .SetEnum("fileStatus", fileStatus)
219                                 .SetEnum("overlayStatus", overlayStatus)
220                                 .ExecuteUpdate();
221                             return updatedEntities;
222                         }, null);
223
224         }
225 */
226
227 /*
228         public static void UpdateStatus(string absolutePath, FileStatus fileStatus)
229         {
230             if (string.IsNullOrWhiteSpace(absolutePath))
231                 throw new ArgumentNullException("absolutePath");
232             Contract.EndContractBlock();
233
234             ExecuteWithRetry((session, instance) =>
235                         {
236                             const string hqlUpdate =
237                                 "update FileState set FileStatus= :fileStatus where FilePath = :path  ";
238                             var updatedEntities = session.CreateQuery(hqlUpdate)
239                                 .SetString("path", absolutePath.ToLower())
240                                 .SetEnum("fileStatus", fileStatus)
241                                 .ExecuteUpdate();
242                             return updatedEntities;
243                         }, null);
244
245         }
246
247 */
248         public static void RenameState(string oldPath, string newPath)
249         {
250             if (string.IsNullOrWhiteSpace(oldPath))
251                 throw new ArgumentNullException("oldPath");
252             Contract.EndContractBlock();
253
254             ExecuteWithRetry((session, instance) =>
255                         {
256                             const string hqlUpdate =
257                                 "update FileState set FilePath= :newPath where FilePath = :oldPath  ";
258                             var updatedEntities = session.CreateQuery(hqlUpdate)
259                                 .SetString("oldPath", oldPath.ToLower())
260                                 .SetString("newPath", newPath.ToLower())
261                                 .ExecuteUpdate();
262                             return updatedEntities;
263                         }, null);
264
265         }
266
267      /*   public static void UpdateStatus(Guid id, FileStatus fileStatus)
268         {
269
270             ExecuteWithRetry((session, instance) =>
271             {
272                 const string hqlUpdate =
273                     "update FileState set FileStatus= :fileStatus where Id = :id  ";
274                 var updatedEntities = session.CreateQuery(hqlUpdate)
275                     .SetGuid("id", id)
276                     .SetEnum("fileStatus", fileStatus)
277                     .ExecuteUpdate();
278                 return updatedEntities;
279             }, null);
280         }*/
281
282         public static void UpdateChecksum(string absolutePath, string checksum)
283         {
284             if (string.IsNullOrWhiteSpace(absolutePath))
285                 throw new ArgumentNullException("absolutePath");
286             Contract.EndContractBlock();
287
288             ExecuteWithRetry((session, instance) =>
289                         {
290                             const string hqlUpdate = "update FileState set Checksum= :checksum where FilePath = :path  ";
291                             var updatedEntities = session.CreateQuery(hqlUpdate)
292                                 .SetString("path", absolutePath.ToLower())
293                                 .SetString("checksum", checksum)
294                                 .ExecuteUpdate();
295                             return updatedEntities;
296                         }, null);
297
298         }
299
300         public static void ChangeRootPath(string oldPath, string newPath)
301         {
302             if (String.IsNullOrWhiteSpace(oldPath))
303                 throw new ArgumentNullException("oldPath");
304             if (!Path.IsPathRooted(oldPath))
305                 throw new ArgumentException("oldPath must be an absolute path", "oldPath");
306             if (string.IsNullOrWhiteSpace(newPath))
307                 throw new ArgumentNullException("newPath");
308             if (!Path.IsPathRooted(newPath))
309                 throw new ArgumentException("newPath must be an absolute path", "newPath");
310             Contract.EndContractBlock();
311
312             //Ensure the paths end with the same character
313             if (!oldPath.EndsWith("\\"))
314                 oldPath = oldPath + "\\";
315             if (!newPath.EndsWith("\\"))
316                 newPath = newPath + "\\";
317
318                 ExecuteWithRetry((session, instance) =>
319                             {
320                                 const string hqlUpdate =
321                                     "update FileState set FilePath = replace(FilePath,:oldPath,:newPath) where FilePath like :oldPath || '%' ";
322                                 var renames = session.CreateQuery(hqlUpdate)
323                                     .SetString("oldPath", oldPath.ToLower())
324                                     .SetString("newPath", newPath.ToLower())
325                                     .ExecuteUpdate();
326                                 return renames;
327                             }, null);
328         }
329
330         public static Task<FileState> CreateForAsync(string filePath, int blockSize, string algorithm)
331         {
332             if (blockSize <= 0)
333                 throw new ArgumentOutOfRangeException("blockSize");
334             if (String.IsNullOrWhiteSpace(algorithm))
335                 throw new ArgumentNullException("algorithm");
336             Contract.EndContractBlock();
337
338
339             var fileState = new FileState
340                                 {
341                                     FilePath = filePath.ToLower(),
342                                     OverlayStatus = FileOverlayStatus.Unversioned,
343                                     FileStatus = FileStatus.Created,
344                                     Id = Guid.NewGuid()
345                                 };
346
347
348             return fileState.UpdateHashesAsync(blockSize, algorithm);
349         }
350
351         public async Task<FileState> UpdateHashesAsync(int blockSize, string algorithm)
352         {
353             if (blockSize <= 0)
354                 throw new ArgumentOutOfRangeException("blockSize");
355             if (String.IsNullOrWhiteSpace(algorithm))
356                 throw new ArgumentNullException("algorithm");
357             Contract.EndContractBlock();
358
359             //Skip updating the hash for folders
360             if (Directory.Exists(FilePath))
361                 return this;
362
363             var hash = await TaskEx.Run(() =>
364                                             {
365                                                 var info = new FileInfo(FilePath);
366                                                 return info.CalculateHash(blockSize, algorithm);
367                                             });
368
369             Checksum = hash;
370
371             return this;
372         }
373
374         private static void ExecuteWithRetry(NHibernateDelegate call, object state)
375         {
376             int retries = 3;
377             while (retries > 0)
378                 try
379                 {
380                     using (new SessionScope())
381                     {
382                         Execute(call, state);
383                     }
384                 }
385                 catch (ActiveRecordException exc)
386                 {
387                     retries--;
388                     if (retries <= 0)
389                         throw;
390                 }
391                 catch (Exception exc)
392                 {
393                     throw;
394                 }
395
396         }
397     }
398
399     [ActiveRecord("Tags")]
400     public class FileTag : ActiveRecordLinqBase<FileTag>
401     {
402         [PrimaryKey]
403         public int Id { get; set; }
404
405         [Property]
406         public string Name { get; set; }
407
408         [Property]
409         public string Value { get; set; }
410
411         [BelongsTo("FileStateId")]
412         public FileState FileState { get; set; }
413
414     }
415    
416 }