Added pithos.okeanos.io in the list of servers. The user_catalog url is now derived...
[pithos-ms-client] / trunk / Pithos.Core / Agents / StatusAgent.cs
1 #region\r
2 /* -----------------------------------------------------------------------\r
3  * <copyright file="StatusAgent.cs" company="GRNet">\r
4  * \r
5  * Copyright 2011-2012 GRNET S.A. All rights reserved.\r
6  *\r
7  * Redistribution and use in source and binary forms, with or\r
8  * without modification, are permitted provided that the following\r
9  * conditions are met:\r
10  *\r
11  *   1. Redistributions of source code must retain the above\r
12  *      copyright notice, this list of conditions and the following\r
13  *      disclaimer.\r
14  *\r
15  *   2. Redistributions in binary form must reproduce the above\r
16  *      copyright notice, this list of conditions and the following\r
17  *      disclaimer in the documentation and/or other materials\r
18  *      provided with the distribution.\r
19  *\r
20  *\r
21  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
22  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
24  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
25  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
28  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
32  * POSSIBILITY OF SUCH DAMAGE.\r
33  *\r
34  * The views and conclusions contained in the software and\r
35  * documentation are those of the authors and should not be\r
36  * interpreted as representing official policies, either expressed\r
37  * or implied, of GRNET S.A.\r
38  * </copyright>\r
39  * -----------------------------------------------------------------------\r
40  */\r
41 #endregion\r
42 using System;\r
43 using System.Collections.Generic;\r
44 using System.ComponentModel.Composition;\r
45 using System.Data;\r
46 using System.Data.SqlServerCe;\r
47 using System.Diagnostics;\r
48 using System.Diagnostics.Contracts;\r
49 using System.IO;\r
50 using System.Linq;\r
51 using System.Reflection;\r
52 using System.Threading;\r
53 using System.Threading.Tasks;\r
54 using NHibernate;\r
55 using NHibernate.Cfg;\r
56 using NHibernate.Cfg.MappingSchema;\r
57 using NHibernate.Criterion;\r
58 using NHibernate.Dialect;\r
59 using NHibernate.Linq;\r
60 using NHibernate.Mapping.ByCode;\r
61 using NHibernate.Tool.hbm2ddl;\r
62 using Pithos.Interfaces;\r
63 using Pithos.Network;\r
64 using log4net;\r
65 using Environment = System.Environment;\r
66 \r
67 namespace Pithos.Core.Agents\r
68 {\r
69     [Export(typeof(IStatusChecker)),Export(typeof(IStatusKeeper))]\r
70     public class StatusAgent:IStatusChecker,IStatusKeeper\r
71     {\r
72         private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);\r
73 \r
74         [System.ComponentModel.Composition.Import]\r
75         public IPithosSettings Settings { get; set; }\r
76 \r
77         [System.ComponentModel.Composition.Import]\r
78         public IStatusNotification StatusNotification { get; set; }\r
79 \r
80         //private Agent<Action> _persistenceAgent;\r
81 \r
82 \r
83 \r
84         public StatusAgent()\r
85         {\r
86             var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);\r
87 \r
88             _pithosDataPath = Path.Combine(appDataPath, "GRNET\\PITHOS");\r
89             if (!Directory.Exists(_pithosDataPath))\r
90                 Directory.CreateDirectory(_pithosDataPath);\r
91 \r
92             var dbPath = Path.Combine(_pithosDataPath, "pithos.sdf");\r
93 \r
94             //MigrateOldDb(dbPath, appDataPath);\r
95 \r
96 \r
97             var cfg = Configure(dbPath);\r
98 \r
99             var connectionString = "Data Source=" + dbPath;\r
100             using (var sqlCeEngine = new SqlCeEngine(connectionString))\r
101             {\r
102                 if (!File.Exists(dbPath))\r
103                 {\r
104                     sqlCeEngine.CreateDatabase();\r
105                     new SchemaExport(cfg).Execute(true, true, false);\r
106                     _factory = cfg.BuildSessionFactory();\r
107                     using (var session = _factory.OpenStatelessSession())\r
108                     {\r
109                         session.Insert(new PithosVersion {Id = 1, Version = "0.0.0.0"});\r
110                     }\r
111                 }\r
112                 else\r
113                 {\r
114                     try\r
115                     {\r
116                         if (!sqlCeEngine.Verify(VerifyOption.Enhanced))\r
117                             sqlCeEngine.Repair(connectionString, RepairOption.RecoverAllOrFail);\r
118                     }\r
119                     catch(SqlCeException ex)\r
120                     {\r
121                         //Rethrow except for sharing errors while repairing\r
122                         if (ex.NativeError != 25035)\r
123                             throw;\r
124                     }\r
125 \r
126                     \r
127                     var update = new SchemaUpdate(cfg);\r
128                     update.Execute(script=>Log.WarnFormat("[DBUPDATE] : {0}",script),true);\r
129                     _factory = cfg.BuildSessionFactory();                    \r
130                 }\r
131                 UpgradeDatabase();\r
132             }\r
133         }\r
134 \r
135         private void UpgradeDatabase()\r
136         {\r
137             using (var session = _factory.OpenSession())\r
138             {\r
139 \r
140                 var storedVersion = session.Get<PithosVersion>(1);\r
141                 var actualVersion = Assembly.GetEntryAssembly().GetName().Version;\r
142 \r
143                 if (actualVersion == new Version(storedVersion.Version)) \r
144                     return;\r
145                 \r
146                 storedVersion.Version = actualVersion.ToString();\r
147                 session.Update(storedVersion);\r
148                 session.Flush();\r
149             }\r
150         }\r
151 \r
152 \r
153         private static Configuration Configure(string pithosDbPath)\r
154         {\r
155             if (String.IsNullOrWhiteSpace(pithosDbPath))\r
156                 throw new ArgumentNullException("pithosDbPath");\r
157             if (!Path.IsPathRooted(pithosDbPath))\r
158                 throw new ArgumentException("path must be a rooted path", "pithosDbPath");\r
159             Contract.EndContractBlock();\r
160 \r
161 \r
162             var cfg = new Configuration();                \r
163                 cfg.DataBaseIntegration(db=>\r
164                                          {\r
165                                              db.Dialect<MsSqlCe40Dialect>();\r
166                                              db.ConnectionString = "Data Source=" + pithosDbPath;\r
167                                              db.AutoCommentSql = true;\r
168                                              db.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;\r
169                                              db.SchemaAction = SchemaAutoAction.Update;\r
170                                              db.LogSqlInConsole = false;                                             \r
171                                          })                                                     \r
172                 .SessionFactory()                      \r
173                     .GenerateStatistics()                    \r
174                     .Integrate.Schema\r
175                         .Updating();            \r
176             var mapping = GetMapping();            \r
177             cfg.AddMapping(mapping);\r
178 \r
179             return cfg;\r
180         }\r
181 \r
182         private static HbmMapping GetMapping()\r
183         {\r
184             var mapper = new ModelMapper();\r
185             mapper.Class<FileState>(fm =>\r
186                                         {\r
187                                             fm.Id(x => x.Id, m => m.Generator(Generators.GuidComb));\r
188                                             fm.Property(x => x.ObjectID, m =>\r
189                                             {\r
190                                                 m.Index("IX_FileState_ObjectID");\r
191                                             });\r
192                                             fm.Property(x => x.FilePath, m =>\r
193                                             {\r
194                                                 m.Unique(true);\r
195                                                 m.UniqueKey("U_FileState_FilePath");\r
196                                                 m.Index("IX_FileState_FilePath");                                                \r
197                                                 m.Length(1024);\r
198                                             });\r
199                                             fm.Property(x => x.OverlayStatus);\r
200                                             fm.Property(x => x.FileStatus);\r
201                                             fm.Property(x => x.ConflictReason);\r
202                                             fm.Property(x => x.Checksum, m => m.Length(64));\r
203                                             fm.Property(x => x.ETag, m => m.Length(64));\r
204                                             fm.Property(x => x.Hashes, m => m.Type(NHibernateUtil.StringClob));\r
205                                             fm.Property(x => x.LastWriteDate);\r
206                                             fm.Property(x => x.LastLength);\r
207                                             fm.Property(x => x.Version);\r
208                                             fm.Property(x => x.VersionTimeStamp);\r
209                                             fm.Property(x => x.IsShared);\r
210                                             fm.Property(x => x.SharedBy);\r
211                                             fm.Property(x => x.ShareWrite);\r
212                                             fm.Property(x => x.IsFolder);\r
213                                             fm.Property(x => x.Modified);                                            \r
214                                         });\r
215             mapper.Class<PithosVersion>(fm =>\r
216                                         {\r
217                                             fm.Id(x => x.Id, m => m.Generator(Generators.Assigned));\r
218                                             fm.Property(x => x.Version, m => m.Length(20));\r
219                                         });\r
220 \r
221 \r
222             var mapping = mapper.CompileMappingFor(new[] {typeof (FileState),typeof(PithosVersion)});\r
223             return mapping;\r
224         }\r
225 \r
226         public void StartProcessing(CancellationToken token)\r
227         {\r
228            \r
229             \r
230         }\r
231 \r
232        \r
233 \r
234         public void Stop()\r
235         {\r
236           \r
237         }\r
238 \r
239 \r
240         public void ProcessExistingFiles(IEnumerable<FileInfo> existingFiles)\r
241         {\r
242             if (existingFiles == null)\r
243                 throw new ArgumentNullException("existingFiles");\r
244             Contract.EndContractBlock();\r
245 \r
246             //Find new or matching files with a left join to the stored states\r
247             using (var session = _factory.OpenSession())\r
248             {\r
249 \r
250                 var fileStates = session.Query<FileState>().ToList();\r
251                 var currentFiles = from file in existingFiles\r
252                                    join state in fileStates on file.FullName.ToLower() equals state.FilePath.ToLower()\r
253                                        into\r
254                                        gs\r
255                                    from substate in gs.DefaultIfEmpty()\r
256                                    select Tuple.Create(file, substate);\r
257 \r
258                 //To get the deleted files we must get the states that have no corresponding\r
259                 //files. \r
260                 //We can't use the File.Exists method inside a query, so we get all file paths from the states\r
261                 var statePaths = (from state in fileStates\r
262                                   select new {state.Id, state.FilePath}).ToList();\r
263                 //and check each one\r
264                 var missingStates = (from path in statePaths\r
265                                      where !File.Exists(path.FilePath) && !Directory.Exists(path.FilePath)\r
266                                      select path.Id).ToList();\r
267                 //Finally, retrieve the states that correspond to the deleted files            \r
268                 var deletedFiles = from state in fileStates\r
269                                    where missingStates.Contains(state.Id)\r
270                                    select Tuple.Create(default(FileInfo), state);\r
271 \r
272                 var pairs = currentFiles.Union(deletedFiles).ToList();\r
273 \r
274                 i = 1;\r
275                 var total = pairs.Count;\r
276                 foreach (var pair in pairs)\r
277                 {\r
278                     ProcessFile(session,total, pair);\r
279                 }\r
280                 session.Flush();\r
281             }\r
282         }\r
283 \r
284         int i = 1;\r
285 \r
286         private void ProcessFile(ISession session,int total, System.Tuple<FileInfo, FileState> pair)\r
287         {\r
288             var idx = Interlocked.Increment(ref i);\r
289             using (StatusNotification.GetNotifier("Indexing file {0} of {1}", "Indexed file {0} of {1} ", true,idx, total))\r
290             {\r
291                 var fileState = pair.Item2;\r
292                 var file = pair.Item1;\r
293                 if (fileState == null)\r
294                 {\r
295                     //This is a new file                        \r
296                     var createState = FileState.CreateFor(file,StatusNotification);                    \r
297                     session.Save(createState);\r
298                     //_persistenceAgent.Post(createState.Create);\r
299                 }\r
300                 else if (file == null)\r
301                 {\r
302                     //This file was deleted while we were down. We should mark it as deleted\r
303                     //We have to go through UpdateStatus here because the state object we are using\r
304                     //was created by a different ORM session.\r
305                     UpdateStatusDirect(session,fileState.Id, FileStatus.Deleted);\r
306                     //_persistenceAgent.Post(() => UpdateStatusDirect((Guid) fileState.Id, FileStatus.Deleted));\r
307                 }\r
308                 //else\r
309                 //{\r
310                 //    //This file has a matching state. Need to check for possible changes\r
311                 //    //To check for changes, we use the cheap (in CPU terms) MD5 algorithm\r
312                 //    //on the entire file.\r
313 \r
314                 //    var hashString = file.ComputeShortHash(StatusNotification);\r
315                 //    Debug.Assert(hashString.Length==32);\r
316 \r
317 \r
318                 //    //TODO: Need a way to attach the hashes to the filestate so we don't\r
319                 //    //recalculate them each time a call to calculate has is made\r
320                 //    //We can either store them to the filestate or add them to a \r
321                 //    //dictionary\r
322 \r
323                 //    //If the hashes don't match the file was changed\r
324                 //    if (fileState.ETag != hashString)\r
325                 //    {\r
326                 //        _persistenceAgent.Post(() => UpdateStatusDirect((Guid) fileState.Id, FileStatus.Modified));\r
327                 //    }\r
328                 //}\r
329             }\r
330         }\r
331 \r
332 \r
333         private int UpdateStatusDirect(ISession session,Guid id, FileStatus status)\r
334         {\r
335             using (ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))\r
336             {\r
337 \r
338                 try\r
339                 {\r
340                         //var updatecmd = session.CreateSQLQuery(\r
341                     var updatecmd = session.CreateQuery(\r
342                         "update FileState set FileStatus= :fileStatus, Modified=:modified where Id = :id  ")\r
343                         .SetGuid("id", id)\r
344                         .SetEnum("fileStatus", status)\r
345                         .SetDateTime("modified",DateTime.Now);\r
346                     var affected = updatecmd.ExecuteUpdate();\r
347                         session.Flush();\r
348                     return affected;\r
349                 }\r
350                 catch (Exception exc)\r
351                 {\r
352                     Log.Error(exc.ToString());\r
353                     throw;\r
354                 }\r
355             }\r
356         }\r
357 \r
358 \r
359         public string BlockHash { get; set; }\r
360 \r
361         public int BlockSize { get; set; }\r
362 \r
363         public void ChangeRoots(string oldPath, string newPath)\r
364         {\r
365             if (String.IsNullOrWhiteSpace(oldPath))\r
366                 throw new ArgumentNullException("oldPath");\r
367             if (!Path.IsPathRooted(oldPath))\r
368                 throw new ArgumentException("oldPath must be an absolute path", "oldPath");\r
369             if (String.IsNullOrWhiteSpace(newPath))\r
370                 throw new ArgumentNullException("newPath");\r
371             if (!Path.IsPathRooted(newPath))\r
372                 throw new ArgumentException("newPath must be an absolute path", "newPath");\r
373             Contract.EndContractBlock();\r
374 \r
375             ChangeRootPath(oldPath,newPath);\r
376 \r
377         }\r
378 \r
379 \r
380 \r
381         private readonly string _pithosDataPath;\r
382         private readonly ISessionFactory _factory;\r
383 \r
384         public FileState GetStateByFilePath(string path)\r
385         {\r
386             if (String.IsNullOrWhiteSpace(path))\r
387                 throw new ArgumentNullException("path");\r
388             if (!Path.IsPathRooted(path))\r
389                 throw new ArgumentException("The path must be rooted", "path");\r
390             Contract.EndContractBlock();\r
391 \r
392             try\r
393             {\r
394                 \r
395                 using(var session=_factory.OpenStatelessSession())\r
396                 {\r
397                     var state=session.Query<FileState>().SingleOrDefault(s => s.FilePath == path);\r
398                     if (state==null)\r
399                         return null;\r
400                     state.FilePath=state.FilePath??String.Empty;\r
401                     state.OverlayStatus = state.OverlayStatus ??FileOverlayStatus.Unversioned;\r
402                     state.FileStatus = state.FileStatus ?? FileStatus.Missing;\r
403                     state.Checksum = state.Checksum ?? String.Empty;\r
404                     state.ETag = state.ETag ?? String.Empty;\r
405                     state.SharedBy = state.SharedBy ?? String.Empty;\r
406                     return state;\r
407                 }\r
408 \r
409             }\r
410             catch (Exception exc)\r
411             {\r
412                 Log.ErrorFormat(exc.ToString());\r
413                 throw;\r
414             }            \r
415         }\r
416 \r
417         public FileOverlayStatus GetFileOverlayStatus(string path)\r
418         {\r
419             if (String.IsNullOrWhiteSpace(path))\r
420                 throw new ArgumentNullException("path");\r
421             if (!Path.IsPathRooted(path))\r
422                 throw new ArgumentException("The path must be rooted", "path");\r
423             Contract.EndContractBlock();\r
424 \r
425             try\r
426             {\r
427                 \r
428                 using(var session=_factory.OpenStatelessSession())\r
429                 {\r
430                     return (from state in session.Query<FileState>()\r
431                             where state.FilePath == path\r
432                             select state.OverlayStatus)\r
433                             .Single()\r
434                             .GetValueOrDefault(FileOverlayStatus.Unversioned);\r
435                 }\r
436             }\r
437             catch (Exception exc)\r
438             {\r
439                 Log.ErrorFormat(exc.ToString());\r
440                 return FileOverlayStatus.Unversioned;\r
441             }\r
442         }\r
443 \r
444         public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus)\r
445         {\r
446             if (String.IsNullOrWhiteSpace(path))\r
447                 throw new ArgumentNullException("path");\r
448             if (!Path.IsPathRooted(path))\r
449                 throw new ArgumentException("The path must be rooted","path");\r
450             Contract.EndContractBlock();\r
451 \r
452             StoreOverlayStatus(path,overlayStatus);\r
453         }\r
454 \r
455         public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus, string conflictReason)\r
456         {\r
457             if (String.IsNullOrWhiteSpace(path))\r
458                 throw new ArgumentNullException("path");\r
459             if (!Path.IsPathRooted(path))\r
460                 throw new ArgumentException("The path must be rooted", "path");\r
461             Contract.EndContractBlock();\r
462 \r
463             Debug.Assert(!path.Contains(FolderConstants.CacheFolder));\r
464             Debug.Assert(!path.EndsWith(".ignore"));\r
465             using (ThreadContext.Stacks["StatusAgent"].Push("SetFileState"))\r
466             {\r
467 \r
468                 try\r
469                 {\r
470 \r
471                     using (var session = _factory.OpenSession())\r
472                     using (var tx=session.BeginTransaction(IsolationLevel.ReadCommitted))\r
473                     {\r
474                         var state = session.Query<FileState>().FirstOrDefault(s => s.FilePath == path)\r
475                                           ?? \r
476                                           ((path.Length<=260) \r
477                                                ?FileState.CreateFor(FileInfoExtensions.FromPath(path), StatusNotification)\r
478                                                :new FileState{\r
479                                                    FilePath = path,\r
480                                                    OverlayStatus = FileOverlayStatus.Conflict,\r
481                                                    FileStatus = Pithos.Core.FileStatus.Conflict,               \r
482                                                    ETag=String.Empty,\r
483                                                    //LastMD5=md5,\r
484                                                    LastWriteDate = DateTime.Today,\r
485                                                    LastLength = 0,\r
486                                                    IsFolder=Directory.Exists(path),\r
487                                                    Modified=DateTime.Now\r
488                                                });\r
489                         state.FileStatus = fileStatus;\r
490                         state.OverlayStatus = overlayStatus;\r
491                         state.ConflictReason = conflictReason;\r
492                         state.Modified = DateTime.Now;\r
493                         session.SaveOrUpdate(state);\r
494 /*\r
495                         //var updatecmd = session.CreateSQLQuery("update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus,ConflictReason= :conflictReason where FilePath = :path ")\r
496                         var updatecmd = session.CreateQuery("update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus,ConflictReason= :conflictReason, Modified=:modified where FilePath = :path")\r
497                             .SetString("path", path)\r
498                             .SetEnum("fileStatus", fileStatus)\r
499                             .SetEnum("overlayStatus", overlayStatus)\r
500                             .SetString("conflictReason", conflictReason)\r
501                             .SetDateTime("modified",DateTime.Now);\r
502                         var affected = updatecmd.ExecuteUpdate();\r
503 \r
504                         if (affected == 0)\r
505                         {\r
506                             //Can happen when downloading a new file\r
507                             var createdState = FileState.CreateFor(FileInfoExtensions.FromPath(path), StatusNotification);\r
508                             createdState.FileStatus = fileStatus;\r
509                             createdState.OverlayStatus = overlayStatus;                            \r
510                             createdState.ConflictReason = conflictReason;\r
511                             session.Save(createdState);\r
512                             //createdState.Create();\r
513                         }*/\r
514                         session.Flush();\r
515                         tx.Commit();                        \r
516                     }\r
517                 }\r
518                 catch (Exception exc)\r
519                 {\r
520                     Log.Error(exc.ToString());\r
521                     throw;\r
522                 }\r
523             }            \r
524         }\r
525 \r
526 \r
527         public void StoreInfo(string path, ObjectInfo objectInfo, TreeHash treeHash)\r
528         {\r
529             if (String.IsNullOrWhiteSpace(path))\r
530                 throw new ArgumentNullException("path");\r
531             if (treeHash==null)\r
532                 throw new ArgumentNullException("treeHash");\r
533             if (!Path.IsPathRooted(path))\r
534                 throw new ArgumentException("The path must be rooted", "path");\r
535             if (objectInfo == null)\r
536                 throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");\r
537             Contract.EndContractBlock();\r
538 \r
539             StoreInfoDirect(path, objectInfo, treeHash);\r
540 \r
541         }\r
542 \r
543         public void StoreInfo(string path, ObjectInfo objectInfo)\r
544         {\r
545             if (String.IsNullOrWhiteSpace(path))\r
546                 throw new ArgumentNullException("path");\r
547             if (!Path.IsPathRooted(path))\r
548                 throw new ArgumentException("The path must be rooted", "path");\r
549             if (objectInfo == null)\r
550                 throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");\r
551             Contract.EndContractBlock();\r
552 \r
553             StoreInfoDirect(path, objectInfo, null);\r
554 \r
555         }\r
556 \r
557         private void StoreInfoDirect(string path, ObjectInfo objectInfo,TreeHash treeHash)\r
558         {\r
559             try\r
560             {\r
561                     using (var session = _factory.OpenSession())\r
562                     using (var tx=session.BeginTransaction(IsolationLevel.ReadCommitted))\r
563                     {                        \r
564                         //An entry for the new path may exist, \r
565                         IQuery deletecmd = session.CreateQuery(\r
566                             "delete from FileState where FilePath=:path and ObjectID is null")\r
567                             .SetString("path", path);\r
568                         deletecmd.ExecuteUpdate();\r
569 \r
570                         //string md5=treeHash.NullSafe(t=>t.MD5);                        \r
571                         string hashes = treeHash.NullSafe(t => t.ToJson());\r
572 \r
573                         var info = FileInfoExtensions.FromPath(path);\r
574                         var lastWriteTime = info.LastWriteTime;\r
575                         var isFolder = (info is DirectoryInfo);\r
576                         var lastLength = isFolder ? 0 : ((FileInfo) info).Length;\r
577 \r
578                         var state = session.Query<FileState>().SingleOrDefault(s => s.ObjectID == (objectInfo.UUID??"ยง"))   //Handle null UUIDs\r
579                                      ?? session.Query<FileState>().SingleOrDefault(s => s.FilePath == path)\r
580                                      ?? new FileState();\r
581                         state.FilePath = path;\r
582                         state.IsFolder = isFolder;\r
583                         state.LastWriteDate = lastWriteTime;\r
584                         state.LastLength = lastLength;\r
585                         state.Checksum = objectInfo.X_Object_Hash;\r
586                         state.Hashes = hashes;\r
587                         state.Version = objectInfo.Version.GetValueOrDefault();\r
588                         state.VersionTimeStamp = objectInfo.VersionTimestamp;\r
589                         state.ETag = objectInfo.ETag;\r
590                         state.FileStatus = FileStatus.Unchanged;\r
591                         state.OverlayStatus = FileOverlayStatus.Normal;\r
592                         state.ObjectID = objectInfo.UUID;\r
593                         state.Modified = DateTime.Now;\r
594                         session.SaveOrUpdate(state);\r
595 \r
596 \r
597 \r
598                         session.Flush();\r
599                         tx.Commit();\r
600                         if (Log.IsDebugEnabled)\r
601                             Log.DebugFormat("DebugDB [{0}]:[{1}]\r\n{2}", path, objectInfo.UUID, objectInfo.X_Object_Hash);\r
602                     }\r
603             }\r
604             catch (Exception exc)\r
605             {\r
606                 Log.ErrorFormat("Failed to update [{0}]:[{1}]\r\n{2}",path,objectInfo.UUID, exc);\r
607                 throw;\r
608             }\r
609         }\r
610 \r
611 \r
612 \r
613         public void SetFileStatus(string path, FileStatus status)\r
614         {\r
615             if (String.IsNullOrWhiteSpace(path))\r
616                 throw new ArgumentNullException("path");\r
617             if (!Path.IsPathRooted(path))\r
618                 throw new ArgumentException("The path must be rooted", "path");\r
619             Contract.EndContractBlock();\r
620 \r
621             using (ThreadContext.Stacks["StatusAgent"].Push("SetFileStatus"))\r
622             {\r
623 \r
624                 try\r
625                 {                    \r
626                     using (var session = _factory.OpenSession())\r
627                     using (var tx=session.BeginTransaction(IsolationLevel.ReadCommitted))\r
628                     {\r
629 \r
630                         //var updatecmd = session.CreateSQLQuery(\r
631                         var updatecmd = session.CreateQuery(\r
632                             "update FileState set FileStatus= :fileStatus, Modified=:modified where FilePath = :path ")\r
633                             .SetString("path", path)\r
634                             .SetEnum("fileStatus", status)\r
635                             .SetDateTime("modified",DateTime.Now);\r
636                         var affected = updatecmd.ExecuteUpdate();\r
637 \r
638                         if (affected == 0)\r
639                         {                            \r
640                             var createdState = FileState.CreateFor(FileInfoExtensions.FromPath(path), StatusNotification);\r
641                             createdState.FileStatus = status;\r
642                             session.Save(createdState);\r
643                         }\r
644                         session.Flush();\r
645                         tx.Commit();\r
646                     }\r
647                 }\r
648                 catch (Exception exc)\r
649                 {\r
650                     Log.Error(exc.ToString());\r
651                     throw;\r
652                 }\r
653             }            \r
654         }\r
655 \r
656         public FileStatus GetFileStatus(string path)\r
657         {\r
658             if (String.IsNullOrWhiteSpace(path))\r
659                 throw new ArgumentNullException("path");\r
660             if (!Path.IsPathRooted(path))\r
661                 throw new ArgumentException("The path must be rooted", "path");\r
662             Contract.EndContractBlock();\r
663 \r
664             \r
665             using(var session=_factory.OpenStatelessSession())\r
666                 return (from state in session.Query<FileState>()\r
667                         select state.FileStatus).SingleOrDefault()??FileStatus.Missing;\r
668         }\r
669 \r
670         /// <summary>\r
671         /// Deletes the status of the specified file\r
672         /// </summary>\r
673         /// <param name="path"></param>\r
674         public void ClearFileStatus(string path)\r
675         {\r
676             if (String.IsNullOrWhiteSpace(path))\r
677                 throw new ArgumentNullException("path");\r
678             if (!Path.IsPathRooted(path))\r
679                 throw new ArgumentException("The path must be rooted", "path");\r
680             Contract.EndContractBlock();\r
681             using(var session=_factory.OpenSession())\r
682             {\r
683                 DeleteDirect(session,path);\r
684                 session.Flush();                \r
685             }\r
686         }\r
687 \r
688         /// <summary>\r
689         /// Deletes the status of the specified folder and all its contents\r
690         /// </summary>\r
691         /// <param name="path"></param>\r
692         public void ClearFolderStatus(string path)\r
693         {\r
694             if (String.IsNullOrWhiteSpace(path))\r
695                 throw new ArgumentNullException("path");\r
696             if (!Path.IsPathRooted(path))\r
697                 throw new ArgumentException("The path must be rooted", "path");\r
698             Contract.EndContractBlock();\r
699             using (ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))\r
700             {\r
701 \r
702                 try\r
703                 {\r
704                     using (var session = _factory.OpenSession())\r
705                     {\r
706                         DeleteDirect(session,path);\r
707                         session.Flush();\r
708                     }\r
709                 }\r
710                 catch (Exception exc)\r
711                 {\r
712                     Log.Error(exc.ToString());\r
713                     throw;\r
714                 }\r
715             }            \r
716         }\r
717 \r
718         public IEnumerable<FileState> GetChildren(FileState fileState)\r
719         {\r
720             if (fileState == null)\r
721                 throw new ArgumentNullException("fileState");\r
722             Contract.EndContractBlock();\r
723 \r
724             var session = _factory.GetCurrentSession();\r
725             var children = from state in session.Query<FileState>()\r
726                            where state.FilePath.StartsWith(fileState.FilePath + "\\")\r
727                            select state;\r
728             return children;\r
729         }\r
730 \r
731         public void EnsureFileState(string path)\r
732         {\r
733             var existingState = GetStateByFilePath(path);\r
734             if (existingState != null)\r
735                 return;\r
736             var fileInfo = FileInfoExtensions.FromPath(path);\r
737             \r
738             using (var session=_factory.OpenSession())\r
739             {\r
740                 var newState = FileState.CreateFor(fileInfo,StatusNotification);\r
741                 newState.FileStatus=FileStatus.Missing;\r
742                 session.SaveOrUpdate(newState);\r
743                 session.Flush();\r
744                 //_persistenceAgent.PostAndAwait(newState.CreateAndFlush).Wait();\r
745             }\r
746 \r
747         }\r
748 \r
749         private void DeleteDirect(ISession session,string filePath)\r
750         {\r
751             using (ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))\r
752             {\r
753 \r
754                 try\r
755                 {\r
756                     var deletes= session.CreateQuery("delete from FileState where FilePath = :path")\r
757                         .SetParameter("path", filePath)\r
758                         .ExecuteUpdate();                    \r
759                 }\r
760                 catch (Exception exc)\r
761                 {\r
762                     Log.Error(exc.ToString());\r
763                     throw;\r
764                 }\r
765             }\r
766         }\r
767 \r
768 \r
769 \r
770 \r
771         public void CleanupOrphanStates()\r
772         {\r
773             //Orphan states are those that do not correspond to an account, ie. their paths\r
774             //do not start with the root path of any registered account\r
775 \r
776             var roots=(from account in Settings.Accounts\r
777                       select account.RootPath).ToList();\r
778 \r
779             using (var session = _factory.OpenSession())\r
780             {\r
781                 var allStates = from state in session.Query<FileState>()\r
782                                 select state.FilePath;\r
783 \r
784                 foreach (var statePath in allStates)\r
785                 {\r
786                     if (!roots.Any(root => statePath.StartsWith(root, StringComparison.InvariantCultureIgnoreCase)))\r
787                         this.DeleteDirect(session,statePath);\r
788                 }\r
789                 session.Flush();\r
790             }\r
791         }\r
792 \r
793         public void SaveCopy<T>(T state) where T:class\r
794         {\r
795             using (var session = _factory.OpenSession())\r
796             {\r
797                 session.Merge(state);\r
798                 session.Flush();\r
799             }\r
800         }\r
801 \r
802         public void CleanupStaleStates(AccountInfo accountInfo, List<ObjectInfo> objectInfos)\r
803         {\r
804             if (accountInfo == null)\r
805                 throw new ArgumentNullException("accountInfo");\r
806             if (objectInfos == null)\r
807                 throw new ArgumentNullException("objectInfos");\r
808             Contract.EndContractBlock();\r
809             \r
810 \r
811 \r
812             //Stale states are those that have no corresponding local or server file\r
813             \r
814 \r
815             var agent=FileAgent.GetFileAgent(accountInfo);\r
816 \r
817             var localFiles=agent.EnumerateFiles();\r
818             var localSet = new HashSet<string>(localFiles);\r
819 \r
820             //RelativeUrlToFilePath will fail for\r
821             //infos of accounts, containers which have no Name\r
822 \r
823             var serverFiles = from info in objectInfos\r
824                               where info.Name != null\r
825                               select Path.Combine(accountInfo.AccountPath,info.RelativeUrlToFilePath(accountInfo.UserName));\r
826             var serverSet = new HashSet<string>(serverFiles);\r
827 \r
828             using (var session = _factory.OpenSession())\r
829             {\r
830 \r
831                 var allStates = from state in session.Query<FileState>()\r
832                                 where state.FilePath.StartsWith(agent.RootPath)\r
833                                 select state.FilePath;\r
834                 var stateSet = new HashSet<string>(allStates);\r
835                 stateSet.ExceptWith(serverSet);\r
836                 stateSet.ExceptWith(localSet);\r
837 \r
838                 foreach (var remainder in stateSet)\r
839                 {\r
840                     DeleteDirect(session,remainder);\r
841                 }\r
842                 session.Flush();\r
843             }\r
844         }\r
845 \r
846         public static TreeHash CalculateTreeHash(FileSystemInfo fileInfo, AccountInfo accountInfo, FileState fileState, byte hashingParallelism, CancellationToken cancellationToken, IProgress<HashProgress> progress)\r
847         {\r
848             fileInfo.Refresh();\r
849             //If the file doesn't exist, return the empty treehash\r
850             if (!fileInfo.Exists)\r
851                 return TreeHash.Empty;\r
852 \r
853             //FileState may be null if there is no stored state for this file\r
854             //if (fileState==null)\r
855                 return TaskEx.Run(async () =>await Signature.CalculateTreeHashAsync(fileInfo,\r
856                                                  accountInfo.BlockSize,\r
857                                                  accountInfo.BlockHash,\r
858                                                  hashingParallelism,\r
859                                                  cancellationToken, progress).ConfigureAwait(false)).Result;\r
860             //Can we use the stored hashes?\r
861             //var localTreeHash = fileState.LastMD5 == Signature.CalculateMD5(fileInfo)\r
862             //                        ? TreeHash.Parse(fileState.Hashes)\r
863             //                        : Signature.CalculateTreeHashAsync(fileInfo,\r
864             //                                                           accountInfo.BlockSize,\r
865             //                                                           accountInfo.BlockHash,\r
866             //                                                           hashingParallelism,\r
867             //                                                           cancellationToken, progress);\r
868             //return localTreeHash;\r
869         }\r
870 \r
871 \r
872 \r
873         private object ExecuteWithRetry(Func<ISession, object, object> call, object state)\r
874         {\r
875             int retries = 3;\r
876             while (retries > 0)\r
877                 try\r
878                 {\r
879                     using (var session=_factory.OpenSession())\r
880                     {\r
881                         var result=call(session, state);\r
882                         session.Flush();\r
883                         return result;\r
884                     }\r
885                 }\r
886                 catch (Exception/* ActiveRecordException */)\r
887                 {\r
888                     retries--;\r
889                     if (retries <= 0)\r
890                         throw;\r
891                 }\r
892             return null;\r
893         }\r
894 \r
895         //TODO: Must separate between UpdateChecksum and UpdateFileHashes\r
896 \r
897         public  void ChangeRootPath(string oldPath, string newPath)\r
898         {\r
899             if (String.IsNullOrWhiteSpace(oldPath))\r
900                 throw new ArgumentNullException("oldPath");\r
901             if (!Path.IsPathRooted(oldPath))\r
902                 throw new ArgumentException("oldPath must be an absolute path", "oldPath");\r
903             if (string.IsNullOrWhiteSpace(newPath))\r
904                 throw new ArgumentNullException("newPath");\r
905             if (!Path.IsPathRooted(newPath))\r
906                 throw new ArgumentException("newPath must be an absolute path", "newPath");\r
907             Contract.EndContractBlock();\r
908 \r
909             //Ensure the paths end with the same character\r
910             if (!oldPath.EndsWith("\\"))\r
911                 oldPath = oldPath + "\\";\r
912             if (!newPath.EndsWith("\\"))\r
913                 newPath = newPath + "\\";\r
914 \r
915             ExecuteWithRetry((session, instance) =>\r
916             {\r
917                 const string hqlUpdate =\r
918                     "update FileState set FilePath = replace(FilePath,:oldPath,:newPath), Modified=:modified where FilePath like :oldPath || '%' ";\r
919                 var renames = session.CreateQuery(hqlUpdate)\r
920                     .SetString("oldPath", oldPath)\r
921                     .SetString("newPath", newPath)\r
922                     .SetDateTime("modified",DateTime.Now)\r
923                     .ExecuteUpdate();\r
924                 return renames;\r
925             }, null);\r
926         }\r
927 \r
928 \r
929         /// <summary>\r
930         /// Mark Unversioned all FileState rows from the database whose path\r
931         /// starts with one of the removed paths\r
932         /// </summary>\r
933         /// <param name="removed"></param>\r
934         public void UnversionPaths(List<string> removed)\r
935         {\r
936             if (removed == null)\r
937                 return;\r
938             if (removed.Count == 0)\r
939                 return;\r
940 \r
941             //Create a disjunction (list of OR statements\r
942             var disjunction = new Disjunction();\r
943             foreach (var path in removed)\r
944             {\r
945                 //with the restriction FileState.FilePath like '@path%'\r
946                 disjunction.Add(Restrictions.On<FileState>(s => s.FilePath)\r
947                                     .IsLike(path, MatchMode.Start));\r
948             }\r
949 \r
950             //Generate a query from the disjunction\r
951             var query = QueryOver.Of<FileState>().Where(disjunction);\r
952 \r
953             using (var session = _factory.OpenSession())\r
954             using (var tx = session.BeginTransaction())\r
955             {\r
956                 \r
957                 var states = query.GetExecutableQueryOver(session).List();\r
958                 foreach (var state in states)\r
959                 {\r
960                     state.FileStatus = FileStatus.Unversioned;\r
961                     state.OverlayStatus = FileOverlayStatus.Unversioned;\r
962                     session.Update(state);\r
963                 }\r
964                 tx.Commit();\r
965                 session.Flush();\r
966             }\r
967 \r
968 \r
969         }\r
970 \r
971         public List<FileState> GetAllStates()\r
972         {\r
973             using(var session=_factory.OpenSession())\r
974             {\r
975                 return session.Query<FileState>().ToList();\r
976             }\r
977         }\r
978 \r
979         public List<string> GetAllStatePaths()\r
980         {\r
981             using (var session = _factory.OpenSession())\r
982             {\r
983                 return session.Query<FileState>().Select(state => state.FilePath).ToList();\r
984             }\r
985         }\r
986 \r
987         public List<FileState> GetConflictStates()\r
988         {\r
989             using (var session = _factory.OpenSession())\r
990             {\r
991                 var fileStates = from state in session.Query<FileState>()\r
992                                  where state.FileStatus == FileStatus.Conflict ||\r
993                                        state.OverlayStatus == FileOverlayStatus.Conflict\r
994                                  select state;\r
995                 return fileStates.ToList();\r
996             }\r
997         }\r
998 \r
999         public void MoveFileState(string oldFullPath, string newFullPath, ObjectInfo objectInfo, TreeHash treeHash)\r
1000         {\r
1001             if (String.IsNullOrWhiteSpace(oldFullPath))\r
1002                 throw new ArgumentNullException("oldFullPath");\r
1003             if (!Path.IsPathRooted(oldFullPath))\r
1004                 throw new ArgumentException("The path must be rooted", "oldFullPath");\r
1005             if (String.IsNullOrWhiteSpace(newFullPath))\r
1006                 throw new ArgumentNullException("newFullPath");\r
1007             if (!Path.IsPathRooted(newFullPath))\r
1008                 throw new ArgumentException("The path must be rooted", "newFullPath");\r
1009             if (treeHash == null)\r
1010                 throw new ArgumentNullException("treeHash");\r
1011             if (objectInfo == null)\r
1012                 throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");\r
1013             Contract.EndContractBlock();\r
1014 \r
1015 \r
1016             try\r
1017             {\r
1018                 using (var session = _factory.OpenSession())\r
1019                 using (var tx=session.BeginTransaction(IsolationLevel.ReadCommitted))\r
1020                 {\r
1021                     //An entry for the new path may exist, \r
1022                     IQuery deletecmd = session.CreateQuery(\r
1023                         "delete from FileState where FilePath=:path and ObjectID is null")\r
1024                         .SetString("path", newFullPath);\r
1025                     deletecmd.ExecuteUpdate();\r
1026 \r
1027                     //string md5=treeHash.NullSafe(t=>t.MD5);                        \r
1028                     string hashes = treeHash.NullSafe(t => t.ToJson());\r
1029 \r
1030                     var info = FileInfoExtensions.FromPath(newFullPath);\r
1031                     var lastWriteTime = info.LastWriteTime;\r
1032                     var isFolder = (info is DirectoryInfo);\r
1033                     var lastLength = isFolder ? 0 : ((FileInfo) info).Length;\r
1034 \r
1035                     var state = session.Query<FileState>().SingleOrDefault(s => s.ObjectID == (objectInfo.UUID??"ยง"))   //Handle null UUIDs\r
1036                                 ?? session.Query<FileState>().SingleOrDefault(s => s.FilePath == newFullPath)\r
1037                                 ?? new FileState();\r
1038                     state.FilePath = newFullPath;\r
1039                     state.IsFolder = isFolder;\r
1040                     state.LastWriteDate = lastWriteTime;\r
1041                     state.LastLength = lastLength;\r
1042                     state.Checksum = objectInfo.X_Object_Hash;\r
1043                     state.Hashes = hashes;\r
1044                     state.Version = objectInfo.Version.GetValueOrDefault();\r
1045                     state.VersionTimeStamp = objectInfo.VersionTimestamp;\r
1046                     state.ETag = objectInfo.ETag;\r
1047                     state.FileStatus = FileStatus.Unchanged;\r
1048                     state.OverlayStatus = FileOverlayStatus.Normal;\r
1049                     state.ObjectID = objectInfo.UUID;\r
1050                     state.Modified = DateTime.Now;\r
1051                     session.SaveOrUpdate(state);\r
1052 \r
1053 \r
1054                     //Delete the old path entry if it still exists (eg. for folders)\r
1055                     session.CreateQuery("delete from FileState where FilePath = :path")\r
1056                         .SetParameter("path", oldFullPath)\r
1057                         .ExecuteUpdate();\r
1058                     \r
1059 \r
1060                     session.Flush();\r
1061                     tx.Commit();\r
1062                     if (Log.IsDebugEnabled)\r
1063                         Log.DebugFormat("DebugDB [{0}]:[{1}]\r\n{2}", newFullPath, objectInfo.UUID, objectInfo.X_Object_Hash);\r
1064                 }\r
1065             }\r
1066             catch (Exception exc)\r
1067             {\r
1068                 Log.ErrorFormat("Failed to update [{0}]:[{1}]\r\n{2}",newFullPath,objectInfo.UUID, exc);\r
1069                 throw;\r
1070             }\r
1071         }\r
1072 \r
1073         public void UpdateFileChecksum(string path, string etag, TreeHash treeHash)\r
1074         {\r
1075             if (String.IsNullOrWhiteSpace(path))\r
1076                 throw new ArgumentNullException("path");\r
1077             if (!Path.IsPathRooted(path))\r
1078                 throw new ArgumentException("The path must be rooted", "path");\r
1079             \r
1080 \r
1081             var hashes = treeHash.ToJson();\r
1082             var topHash = treeHash.TopHash.ToHashString();\r
1083 \r
1084             ExecuteWithRetry((session, instance) =>\r
1085             {\r
1086                 const string hqlUpdate = "update FileState set Checksum= :checksum,Hashes=:hashes,ETag=:etag, Modified=:modified where FilePath = :path ";\r
1087                 var updatedEntities = session.CreateQuery(hqlUpdate)\r
1088                     .SetString("path", path)\r
1089                     .SetString("checksum", topHash)\r
1090                     .SetString("hashes", hashes)\r
1091                     .SetString("etag", etag)\r
1092                     .SetDateTime("modified", DateTime.Now)\r
1093                     .ExecuteUpdate();\r
1094                 return updatedEntities;\r
1095             }, null);\r
1096         }\r
1097 \r
1098         //Store only the hashes\r
1099         public  void UpdateFileHashes(FileInfo file, TreeHash treeHash)\r
1100         {\r
1101             if (file==null)\r
1102                 throw new ArgumentNullException("file");\r
1103             Contract.EndContractBlock();\r
1104 \r
1105             var hashes = treeHash.ToJson();\r
1106             var topHash = treeHash.TopHash.ToHashString();\r
1107 \r
1108             ExecuteWithRetry((session, instance) =>\r
1109             {\r
1110 \r
1111                 const string hqlUpdate = "update FileState set Hashes=:hashes,Modified=:modified,LastWriteDate=:date, LastLength=:length where FilePath = :path ";\r
1112 /*\r
1113                 const string hqlUpdate = "update FileState set Checksum= :checksum,Hashes=:hashes where FilePath = :path ";\r
1114 */\r
1115                 var updatedEntities = session.CreateQuery(hqlUpdate)\r
1116                     .SetString("path", file.FullName)\r
1117                     .SetDateTime("date",file.LastWriteTime)\r
1118                     .SetInt64("length",file.Length)\r
1119                     //.SetString("checksum", topHash)\r
1120                     //                    .SetString("md5",treeHash.MD5)\r
1121                     .SetString("hashes", hashes)\r
1122                     .SetDateTime("modified", DateTime.Now)\r
1123                     .ExecuteUpdate();\r
1124                 return updatedEntities;\r
1125             }, null);\r
1126         }\r
1127 \r
1128 \r
1129         public void RenameState(string oldPath, string newPath)\r
1130         {\r
1131             if (string.IsNullOrWhiteSpace(oldPath))\r
1132                 throw new ArgumentNullException("oldPath");\r
1133             Contract.EndContractBlock();\r
1134 \r
1135             ExecuteWithRetry((session, instance) =>\r
1136             {\r
1137                 const string hqlUpdate =\r
1138                     "update FileState set FilePath= :newPath, Modified=:modified where FilePath = :oldPath ";\r
1139                 var updatedEntities = session.CreateQuery(hqlUpdate)\r
1140                     .SetString("oldPath", oldPath)\r
1141                     .SetString("newPath", newPath)\r
1142                     .SetDateTime("modified", DateTime.Now)\r
1143                     .ExecuteUpdate();\r
1144                 return updatedEntities;\r
1145             }, null);\r
1146 \r
1147         }\r
1148 \r
1149         public void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus)\r
1150         {\r
1151             if (string.IsNullOrWhiteSpace(absolutePath))\r
1152                 throw new ArgumentNullException("absolutePath");\r
1153             Contract.EndContractBlock();\r
1154 \r
1155             using(var session=_factory.OpenSession())\r
1156             {\r
1157                 using (var tx = session.BeginTransaction())\r
1158                 {\r
1159                     var state = session.Query<FileState>().SingleOrDefault(s => s.FilePath == absolutePath) \r
1160                         ?? new FileState{\r
1161                             FilePath = absolutePath,                            \r
1162                             OverlayStatus = newStatus,\r
1163                             ETag = Signature.MERKLE_EMPTY,\r
1164                             //LastMD5=String.Empty,\r
1165                             IsFolder = Directory.Exists(absolutePath),\r
1166                             Modified=DateTime.Now\r
1167                         };\r
1168                     state.OverlayStatus = newStatus;\r
1169                     session.SaveOrUpdate(state);\r
1170                     session.Flush();\r
1171                     tx.Commit();                    \r
1172                 }\r
1173             };\r
1174         }\r
1175 \r
1176     }\r
1177 \r
1178    \r
1179 }\r