Replaced object load and update with direct HQL execution to resolve database locks...
[pithos-ms-client] / trunk / Pithos.Core / FileState.cs
1 // -----------------------------------------------------------------------
2 // <copyright file="FileState.cs" company="Microsoft">
3 // TODO: Update copyright text.
4 // </copyright>
5 // -----------------------------------------------------------------------
6
7 using System.Diagnostics.Contracts;
8 using System.IO;
9 using System.Threading.Tasks;
10 using Castle.ActiveRecord;
11 using Castle.ActiveRecord.Framework;
12 using Pithos.Core.Agents;
13 using Pithos.Interfaces;
14 using log4net;
15
16 namespace Pithos.Core
17 {
18     using System;
19     using System.Collections.Generic;
20     using System.Linq;
21
22     /// <summary>
23     /// TODO: Update summary.
24     /// </summary>
25     [ActiveRecord]
26     public class FileState:ActiveRecordLinqBase<FileState>
27     {
28         private static readonly ILog Log = LogManager.GetLogger("FileState");
29         
30         private string _filePath;
31         private IList<FileTag> _tags=new List<FileTag>();
32
33         [PrimaryKey(PrimaryKeyType.Guid)]
34         public Guid Id { get; set; }
35
36         [Property(Unique=true,UniqueKey="IX_FileState_FilePath")]
37         public string FilePath
38         {
39             get { return _filePath; }
40             set { _filePath = value.ToLower(); }
41         }
42
43         [Property]
44         public FileOverlayStatus OverlayStatus { get; set; }
45
46         [Property]
47         public FileStatus FileStatus { get; set; }
48
49         [Property]
50         public string Checksum { get; set; }
51
52
53         [Property]
54         public long? Version { get; set; }
55
56         [Property]
57         public DateTime? VersionTimeStamp { get; set; }
58
59
60         [Property]
61         public bool IsShared { get; set; }
62
63         [Property]
64         public string SharedBy { get; set; }
65
66         [Property]
67         public bool ShareWrite { get; set; }
68
69
70        [HasMany(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Lazy = true,Inverse=true)]
71         public IList<FileTag> Tags
72         {
73             get { return _tags; }   
74             set { _tags=value;}
75         }
76
77    
78         public static FileState FindByFilePath(string absolutePath)
79         {
80             if (string.IsNullOrWhiteSpace(absolutePath))
81                 throw new ArgumentNullException("absolutePath");
82             Contract.EndContractBlock();
83             try
84             {
85                 return Queryable.FirstOrDefault(s => s.FilePath == absolutePath.ToLower());
86             }
87             catch (Exception ex)
88             {
89                 Log.Error(ex.ToString());
90                 throw;
91             }
92                 
93
94         }
95
96         public static void DeleteByFilePath(string absolutePath)
97         {
98             if(string.IsNullOrWhiteSpace(absolutePath))
99                 throw new ArgumentNullException("absolutePath");
100             Contract.EndContractBlock();
101             
102             Execute((session, instance) =>
103                              {
104                                  const string hqlDelete = "delete FileState where FilePath = :path";                                 
105                                  var deletedEntities = session.CreateQuery(hqlDelete)
106                                          .SetString("path", absolutePath.ToLower())
107                                          .ExecuteUpdate();
108                                  return deletedEntities;
109                              },null);
110             
111         }
112
113         public static void StoreFileStatus(string absolutePath, FileStatus newStatus)
114         {
115             if (string.IsNullOrWhiteSpace(absolutePath))
116                 throw new ArgumentNullException("absolutePath");
117             Contract.EndContractBlock();
118
119             Execute((session, instance) =>
120             {
121                 const string hqlUpdate = "update FileState set FileStatus= :status where FilePath = :path  ";
122                 var updatedEntities = session.CreateQuery(hqlUpdate)
123                         .SetString("path", absolutePath.ToLower())
124                         .SetEnum("status", newStatus)
125                         .ExecuteUpdate();
126                 if (updatedEntities == 0)
127                 {
128                     var newState = new FileState { FilePath = absolutePath, Id = Guid.NewGuid(), FileStatus = newStatus };
129                     newState.CreateAndFlush();
130                 }
131                 return null;
132             }, null);
133
134         }
135
136         public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus)
137         {
138             if (string.IsNullOrWhiteSpace(absolutePath))
139                 throw new ArgumentNullException("absolutePath");
140             Contract.EndContractBlock();
141
142             Execute((session, instance) =>
143             {
144                 const string hqlUpdate = "update FileState set OverlayStatus= :status where FilePath = :path  ";
145                 var updatedEntities = session.CreateQuery(hqlUpdate)
146                         .SetString("path", absolutePath.ToLower())
147                         .SetEnum("status", newStatus)
148                         .ExecuteUpdate();
149                 if (updatedEntities == 0)
150                 {
151                     var newState = new FileState { FilePath = absolutePath, Id = Guid.NewGuid(), OverlayStatus = newStatus };
152                     newState.CreateAndFlush();
153                 }
154                 return null;
155             }, null);
156
157         }
158
159         public static void UpdateStatus(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
160         {
161             if (string.IsNullOrWhiteSpace(absolutePath))
162                 throw new ArgumentNullException("absolutePath");
163             Contract.EndContractBlock();
164
165             Execute((session, instance) =>
166             {
167                 const string hqlUpdate = "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path  ";
168                 var updatedEntities = session.CreateQuery(hqlUpdate)
169                         .SetString("path", absolutePath.ToLower())
170                         .SetEnum("fileStatus", fileStatus)
171                         .SetEnum("overlayStatus", overlayStatus)
172                         .ExecuteUpdate();
173                 return updatedEntities;
174             }, null);
175
176         }
177         public static void UpdateStatus(string absolutePath, FileStatus fileStatus)
178         {
179             if (string.IsNullOrWhiteSpace(absolutePath))
180                 throw new ArgumentNullException("absolutePath");
181             Contract.EndContractBlock();
182
183             Execute((session, instance) =>
184             {
185                 const string hqlUpdate = "update FileState set FileStatus= :fileStatus where FilePath = :path  ";
186                 var updatedEntities = session.CreateQuery(hqlUpdate)
187                         .SetString("path", absolutePath.ToLower())
188                         .SetEnum("fileStatus", fileStatus)                        
189                         .ExecuteUpdate();
190                 return updatedEntities;
191             }, null);
192
193         }
194
195         public static void RenameState(string oldPath, string newPath)
196         {
197             if (string.IsNullOrWhiteSpace(oldPath))
198                 throw new ArgumentNullException("oldPath");
199             Contract.EndContractBlock();
200
201             Execute((session, instance) =>
202             {
203                 const string hqlUpdate = "update FileState set FilePath= :newPath where FilePath = :oldPath  ";
204                 var updatedEntities = session.CreateQuery(hqlUpdate)
205                         .SetString("oldPath", oldPath.ToLower())
206                         .SetString("newPath", newPath.ToLower())                                          
207                         .ExecuteUpdate();
208                 return updatedEntities;
209             }, null);
210
211         }
212
213         public static void UpdateStatus(Guid id, FileStatus fileStatus)
214         {
215             Contract.EndContractBlock();
216
217             Execute((session, instance) =>
218             {
219                 const string hqlUpdate = "update FileState set FileStatus= :fileStatus where Id = :id  ";
220                 var updatedEntities = session.CreateQuery(hqlUpdate)
221                         .SetGuid("id", id)
222                         .SetEnum("fileStatus", fileStatus)                        
223                         .ExecuteUpdate();
224                 return updatedEntities;
225             }, null);
226
227         }
228
229         public static void UpdateChecksum(string absolutePath, string checksum)
230         {
231             if (string.IsNullOrWhiteSpace(absolutePath))
232                 throw new ArgumentNullException("absolutePath");
233             Contract.EndContractBlock();
234
235             Execute((session, instance) =>
236             {
237                 const string hqlUpdate = "update FileState set Checksum= :checksum where FilePath = :path  ";
238                 var updatedEntities = session.CreateQuery(hqlUpdate)
239                         .SetString("path", absolutePath.ToLower())
240                         .SetString("checksum", checksum)                        
241                         .ExecuteUpdate();
242                 return updatedEntities;
243             }, null);
244
245         }
246
247         public static void ChangeRootPath(string oldPath,string newPath)
248         {
249             if (String.IsNullOrWhiteSpace(oldPath))
250                 throw new ArgumentNullException("oldPath");
251             if (!Path.IsPathRooted(oldPath))
252                 throw new ArgumentException("oldPath must be an absolute path", "oldPath");
253             if (string.IsNullOrWhiteSpace(newPath))
254                 throw new ArgumentNullException("newPath");
255             if (!Path.IsPathRooted(newPath))
256                 throw new ArgumentException("newPath must be an absolute path", "newPath");
257             Contract.EndContractBlock();
258
259             //Ensure the paths end with the same character
260             if (!oldPath.EndsWith("\\"))
261                 oldPath = oldPath + "\\";
262             if (!newPath.EndsWith("\\"))
263                 newPath = newPath + "\\";
264
265             using (new TransactionScope())
266             {
267                 Execute((session, instance) =>
268                             {
269                                 const string hqlUpdate =
270                                     "update FileState set FilePath = replace(FilePath,:oldPath,:newPath) where FilePath like :oldPath || '%' ";
271                                 var renames=session.CreateQuery(hqlUpdate)
272                                     .SetString("oldPath", oldPath.ToLower())
273                                     .SetString("newPath", newPath.ToLower())
274                                     .ExecuteUpdate();
275                                 return renames;
276                             }, null);
277             }
278         }
279
280         public static Task<FileState> CreateForAsync(string filePath,int blockSize,string algorithm)
281         {
282             if (blockSize <= 0)
283                 throw new ArgumentOutOfRangeException("blockSize");
284             if (String.IsNullOrWhiteSpace(algorithm))
285                 throw new ArgumentNullException("algorithm");
286             Contract.EndContractBlock();
287
288
289             var fileState = new FileState
290                                 {
291                                     FilePath = filePath, 
292                                     OverlayStatus = FileOverlayStatus.Unversioned, 
293                                     FileStatus = FileStatus.Created,
294                                     Id=Guid.NewGuid()
295                                 };
296
297
298             return fileState.UpdateHashesAsync(blockSize,algorithm);            
299         }
300
301         public Task<FileState> UpdateHashesAsync(int blockSize,string algorithm)
302         {
303             if (blockSize<=0)
304                 throw new ArgumentOutOfRangeException("blockSize");
305             if (String.IsNullOrWhiteSpace(algorithm))
306                 throw new ArgumentNullException("algorithm");
307             Contract.EndContractBlock();
308             
309             //Skip updating the hash for folders
310             if (Directory.Exists(FilePath))
311                 return Task.Factory.FromResult(this);
312
313             var results = Task.Factory.StartNew(() =>
314             {
315                 var info = new FileInfo(FilePath);
316                 return info.CalculateHash(blockSize, algorithm);
317             });
318
319             var state=results.Then(hash =>
320             {
321                 Checksum = hash;
322                 return Task.Factory.FromResult(this);
323             });
324             
325             return state;
326         }
327     }
328
329     [ActiveRecord("Tags")]
330     public class FileTag : ActiveRecordLinqBase<FileTag>
331     {
332         [PrimaryKey]
333         public int Id { get; set; }
334
335         [Property]
336         public string Name { get; set; }
337
338         [Property]
339         public string Value { get; set; }
340
341         [BelongsTo("FileStateId")]
342         public FileState FileState { get; set; }
343
344     }
345    
346 }