1
/****************************************************************************
3
| Copyright (c) 2007 Novell, Inc.
6
| This program is free software; you can redistribute it and/or
7
| modify it under the terms of version 2 of the GNU General Public License as
8
| published by the Free Software Foundation.
10
| This program is distributed in the hope that it will be useful,
11
| but WITHOUT ANY WARRANTY; without even the implied warranty of
12
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
| GNU General Public License for more details.
15
| You should have received a copy of the GNU General Public License
16
| along with this program; if not, contact Novell, Inc.
18
| To contact Novell about this file by physical or electronic mail,
19
| you may find current contact information at www.novell.com
21
| Author: Dale Olds <olds@novell.com>
23
|***************************************************************************/
27
using System.Threading;
28
using System.Collections;
30
using System.Diagnostics;
36
using Simias.Client.Event;
42
// This is used if configure.in detected mono 1.1.13 or newer
43
using Mono.Unix.Native;
53
//---------------------------------------------------------------------------
55
/// class to sync a portion of the file system with a collection
56
/// applying iFolder specific behavior
59
/* TODO: need to handle if we are on a case-insensitive file system and file name
60
* changes only by case? Actually this would be a rather rare optimization and
61
* probably not worth it for the dredger (except perhaps for a directory rename).
62
* If the event system is up, we catch it as a rename. If not, the dredger treats
63
* it as a delete and create. Dredger should always be case sensitive.
66
public class FileWatcher
68
internal static readonly ISimiasLog log = SimiasLogManager.GetLogger(typeof(FileWatcher));
71
/// The collection to monitor.
73
public Collection collection = null;
75
/* TODO: onServer needs to be removed. It controls how tombstones are handled:
76
* they are deleted on the server but left on the client. What it
77
* really needs to be is deleted if there is no upstream server. Perhaps
78
* the best way to handle it would be for this code to always leave a
79
* tombstone, but the sync code would just remove them if there was no
82
bool onServer = false;
83
const string lastDredgeProp = "LastDredgeTime";
84
DateTime dredgeTimeStamp;
85
bool needToDredge = true;
88
public bool NeedToDredge
90
set { needToDredge = value; }
92
DateTime lastDredgeTime = DateTime.MinValue;
97
// string collectionId;
98
internal FileSystemWatcher watcher;
99
Hashtable changes = new Hashtable();
100
Hashtable oldNames = new Hashtable();
101
EventPublisher eventPublisher = new EventPublisher();
104
internal class fileChangeEntry : IComparable
106
internal static int settleTime = 2;
107
internal static int counter = 0;
108
internal FileSystemEventArgs eArgs;
109
internal int eventNumber;
110
internal DateTime eventTime;
112
internal fileChangeEntry(FileSystemEventArgs e)
115
eventNumber = Interlocked.Increment(ref counter);
116
eventTime = DateTime.Now;
119
internal void update(FileSystemEventArgs e)
122
eventTime = DateTime.Now;
125
internal void update()
127
eventTime = DateTime.Now;
130
#region IComparable Members
132
public int CompareTo(object obj)
134
fileChangeEntry cobj = obj as fileChangeEntry;
135
return eventNumber.CompareTo(cobj.eventNumber);
142
/// Creates a dredger for this collection and dredges the system.
144
/// <param name="collection"></param>
145
/// <param name="onServer"></param>
146
public FileWatcher(Collection collection, bool onServer)
148
// TODO: Syncronize the dredger with the sync engine.
149
this.collection = collection;
150
this.onServer = onServer;
151
// this.collectionId = collection.ID;
153
// if (!MyEnvironment.Mono)
155
// We are on .Net use events to watch for changes.
156
DirNode rootDir = collection.GetRootDirectory();
159
string rootPath = collection.GetRootDirectory().GetFullPath(collection);
160
watcher = new FileSystemWatcher(rootPath);
161
log.Debug("New File Watcher at {0}", rootPath);
162
watcher.Changed += new FileSystemEventHandler(OnChanged);
163
watcher.Created += new FileSystemEventHandler(OnCreated);
164
watcher.Deleted += new FileSystemEventHandler(OnDeleted);
165
watcher.Renamed += new RenamedEventHandler(OnRenamed);
166
watcher.Error += new ErrorEventHandler(watcher_Error);
167
watcher.IncludeSubdirectories = true;
168
watcher.EnableRaisingEvents = true;
169
// Now dredge to find any files that were changed while we were down.
176
/// // Delete the specified node.
178
/// <param name="node">The node to delete.</param>
179
void DeleteNode(Node node)
181
Log.log.Debug("File Monitor deleting orphaned node {0}, {1}", node.Name, node.ID);
183
// Check to see if this is a ghost file.
184
// If it is we do not want to delete the node.
185
if (node.Properties.GetSingleProperty(PropertyTags.GhostFile) != null)
188
// Check to see if we have a collision.
189
bool isDir = (collection.BaseType == NodeTypes.DirNodeType);
190
if (collection.HasCollisions(node))
192
Conflict cNode = new Conflict(collection, node);
193
if (cNode.IsFileNameConflict)
195
// This is a name collision make sure that we delete the right node.
196
// Only delete if the file no longer exists.
197
if (Path.GetFileName(cNode.FileNameConflictPath) != node.Name)
199
node = Conflict.GetConflictingNode(collection, node as FileNode);
202
cNode = new Conflict(collection, node);
204
if (File.Exists(cNode.FileNameConflictPath))
209
cNode.DeleteConflictFile();
211
Node[] deleted = collection.Delete(node, PropertyTags.Parent);
212
collection.Commit(deleted);
213
eventPublisher.RaiseEvent(new FileSyncEventArgs(collection.ID, isDir ? ObjectType.Directory : ObjectType.File, true, node.Name, 0, 0, 0, Direction.Local));
217
public static void RenameDirsChildren(Collection collection, DirNode dn, string oldRelativePath)
219
string relativePath = dn.GetRelativePath();
220
// We need to rename all of the children nodes.
221
ArrayList nodeList = new ArrayList();
222
ICSList csnList = collection.Search(PropertyTags.FileSystemPath, oldRelativePath, SearchOp.Begins);
223
foreach (ShallowNode csn in csnList)
229
Node childNode = collection.GetNodeByID(csn.ID);
230
if (childNode != null)
232
Property childRP = childNode.Properties.GetSingleProperty(PropertyTags.FileSystemPath);
235
string newRP = childRP.ValueString;
236
if (newRP.Length > oldRelativePath.Length && newRP.StartsWith(oldRelativePath) && newRP[oldRelativePath.Length] == '/')
238
childRP.SetPropertyValue(newRP.Replace(oldRelativePath, relativePath));
239
childNode.Properties.ModifyNodeProperty(childRP);
240
nodeList.Add(childNode);
245
collection.Commit((Node[])nodeList.ToArray(typeof(Node)));
247
public static void SetRenamePropertyForDirChildren(Collection collection, DirNode dn, string oldRelativePath)
249
string relativePath = dn.GetRelativePath();
250
// We need to rename all of the children nodes.
251
ArrayList nodeList = new ArrayList();
253
ICSList csnList = collection.Search(PropertyTags.FileSystemPath, oldRelativePath, SearchOp.Begins);
254
foreach (ShallowNode csn in csnList)
260
Node childNode = collection.GetNodeByID(csn.ID);
261
if (childNode != null)
263
Property p = new Property(PropertyTags.ReNamed, true);
264
p.LocalProperty = true;
265
childNode.Properties.ModifyProperty(p);
266
nodeList.Add(childNode);
269
log.Debug("SetRenamePropertyForDirChildren child node null");
271
collection.Commit((Node[])nodeList.ToArray(typeof(Node)));
274
bool ExecuteBitSet(string path)
277
if (MyEnvironment.Unix)
279
// Get the posix access flags for owner.
281
if (Syscall.stat(path, out sStat) == 0)
283
if ((sStat.st_mode & FilePermissions.S_IXUSR) != 0)
294
/// Create a FileNode for the specified file.
296
/// <param name="path">The path to the node to create.</param>
297
/// <param name="parentNode">The parent of the node to create.</param>
298
/// <param name="conflict">The node should be created with a conflict.</param>
299
/// <returns>The new FileNode.</returns>
300
FileNode CreateFileNode(string path, DirNode parentNode, bool conflict)
302
if (isSyncFile(path) || collection.HasCollisions(parentNode))
304
FileNode fnode = new FileNode(collection, parentNode, Path.GetFileName(path));
305
if (ExecuteBitSet(path))
307
// The execute bit is set for the user save the value.
308
fnode.Properties.ModifyProperty(SyncFile.ModeProperty, SyncFile.FAMode.Execute);
311
log.Debug("Adding file node for {0} {1}", path, fnode.ID);
312
// Make sure that we support the Simias Name Space.
313
if (!SyncFile.IsNameValid(fnode.Name))
319
// We have a name collision set the collision state.
320
fnode = Conflict.CreateNameConflict(collection, fnode, path) as FileNode;
322
collection.Commit(fnode);
323
eventPublisher.RaiseEvent(new FileSyncEventArgs(collection.ID, ObjectType.File, false, fnode.Name, 0, 0, 0, Direction.Local));
329
/// Modify the FileNode for the changed file.
331
/// <param name="path">The path of the file that has changed.</param>
332
/// <param name="fn">The node to modify.</param>
333
/// <param name="hasChanges">If the node has changes set to true.</param>
334
void ModifyFileNode(string path, BaseFileNode fn, bool hasChanges)
336
// here we are just checking for modified files
337
FileInfo fi = new FileInfo(path);
338
TimeSpan ts = fi.LastWriteTime - fn.LastWriteTime;
340
// Fat32 has a 2 second time resolution, Linux has a 1 second resolution. Check for > 1;
341
if ((fi.Length != fn.Length || ((uint)ts.Seconds > 1)) && (fn.UpdateFileInfo(collection, path)))
344
log.Debug("Updating file node for {0} {1}", path, fn.ID);
346
if (!SyncFile.IsNameValid(fn.Name))
348
// This is a conflict.
349
fn = Conflict.CreateNameConflict(collection, fn, path) as BaseFileNode;
352
bool exAlready = false;
353
if (fn.Properties.GetSingleProperty(SyncFile.ModeProperty) != null)
355
if (ExecuteBitSet(path))
359
// The execute bit is set for the user save the value.
360
fn.Properties.ModifyProperty(SyncFile.ModeProperty, SyncFile.FAMode.Execute);
366
fn.Properties.DeleteSingleProperty(SyncFile.ModeProperty);
371
collection.Commit(fn);
372
eventPublisher.RaiseEvent(new FileSyncEventArgs(collection.ID, ObjectType.File, false, fn.Name, 0, 0, 0, Direction.Local));
378
/// Rename the file node.
380
/// <param name="newName">The new name.</param>
381
/// <param name="node">The node to rename.</param>
382
/// <returns>The renamed node.</returns>
383
BaseFileNode RenameFileNode(string newName, BaseFileNode node)
385
node.Name = Path.GetFileName(newName);
386
string relativePath = GetNormalizedRelativePath(rootPath, newName);
387
node.Properties.ModifyNodeProperty(new Property(PropertyTags.FileSystemPath, Syntax.String, relativePath));
388
// The file may have been modified. If it has, we need to make sure the length is updated.
389
FileInfo fi = new FileInfo(newName);
390
if (fi.Length != node.Length)
391
node.UpdateFileInfo(collection, newName);
393
//set the local rename property for dir children
394
//this local property will be checked and cleard in the the sync method ploadFile()
395
Property p = new Property(PropertyTags.ReNamed, true);
396
p.LocalProperty = true;
397
node.Properties.ModifyProperty(p);
399
// Commit the directory.
400
collection.Commit(node);
405
/// Rename the directory and fixup children.
407
/// <param name="newPath">The new name of the dir.</param>
408
/// <param name="node">The dir node to rename.</param>
409
/// <returns>The modified node.</returns>
410
DirNode RenameDirNode(string newPath, DirNode node)
412
node.Name = Path.GetFileName(newPath);
413
string relativePath = GetNormalizedRelativePath(rootPath, newPath);
414
string oldRelativePath = node.Properties.GetSingleProperty(PropertyTags.FileSystemPath).ValueString;
415
node.Properties.ModifyNodeProperty(new Property(PropertyTags.FileSystemPath, Syntax.String, relativePath));
417
//set the local rename property for dir children
418
//this local property will be checked and cleard in the the sync method ploadFile()
419
SetRenamePropertyForDirChildren(collection, node, oldRelativePath);
421
// Commit the directory.
422
collection.Commit(node);
423
// We need to rename all of the children nodes.
424
RenameDirsChildren(collection, node, oldRelativePath);
425
DoSubtree(newPath, node, node.ID, true);
430
/// Create a DirNode for the specified directory.
432
/// <param name="path">The path to the directory.</param>
433
/// <param name="parentNode">The parent DirNode.</param>
434
/// <param name="conflict">The node should be created with a conflict.</param>
435
/// <returns>The new DirNode.</returns>
436
DirNode CreateDirNode(string path, DirNode parentNode, bool conflict)
438
if (isSyncFile(path))
441
string fName = Path.GetFileName(path);
442
DirNode dnode = new DirNode(collection, parentNode, fName);
443
log.Debug("Adding dir node for {0} {1}", path, dnode.ID);
444
// Make sure that we support the Simias Name Space.
445
if (!SyncFile.IsNameValid(dnode.Name) || SyncFile.DoesNodeExist(collection, parentNode, fName))
451
// We have a name collision set the collision state.
452
dnode = Conflict.CreateNameConflict(collection, dnode, path) as DirNode;
454
collection.Commit(dnode);
455
eventPublisher.RaiseEvent(new FileSyncEventArgs(collection.ID, ObjectType.Directory, false, dnode.Name, 0, 0, 0, Direction.Local));
457
DoSubtree(path, dnode, dnode.ID, true);
465
/// <param name="oldPath"></param>
466
/// <param name="newPath"></param>
467
/// <returns></returns>
468
bool HasParentChanged(string oldPath, string newPath)
470
if (MyEnvironment.Windows)
472
return String.Compare(Path.GetDirectoryName(oldPath), Path.GetDirectoryName(newPath), true) == 0 ? false : true;
476
return (!(Path.GetDirectoryName(oldPath).Equals(Path.GetDirectoryName(newPath))));
483
/// <param name="rootPath"></param>
484
/// <param name="path"></param>
485
/// <returns></returns>
486
public static string GetNormalizedRelativePath(string rootPath, string path)
488
string relPath = path.Replace(rootPath, "");
489
relPath = relPath.TrimStart(Path.DirectorySeparatorChar);
490
if (Path.DirectorySeparatorChar != '/')
491
relPath = relPath.Replace('\\', '/');
496
/// Get a ShallowNode for the named file or directory.
498
/// <param name="path">Path to the file.</param>
499
/// <param name="haveConflict"></param>
500
/// <returns>The ShallowNode for this file.</returns>
501
ShallowNode GetShallowNodeForFile(string path, out bool haveConflict)
503
ShallowNode sNode = null;
504
haveConflict = false;
505
string relPath = GetNormalizedRelativePath(rootPath, path);
508
nodeList = collection.Search(PropertyTags.FileSystemPath, relPath, SearchOp.Equal);
509
foreach (ShallowNode sn in nodeList)
511
if (sn.Name == Path.GetFileName(path))
518
sNode = sNode == null ? sn : sNode;
525
/// Return the parent for this path.
527
/// <param name="path">Path to the file whose parent is wanted.</param>
528
/// <returns></returns>
529
DirNode GetParentNode(string path)
532
ShallowNode sn = GetShallowNodeForFile(Path.GetDirectoryName(path), out haveConflict);
535
return (DirNode)collection.GetNodeByID(sn.ID);
541
/// Check if the file is an internal sync file.
543
/// <param name="name"></param>
544
/// <returns></returns>
545
private bool isSyncFile(string name)
547
string fname = Path.GetFileName(name);
548
return fname.StartsWith(".simias.");
555
/// <param name="parent"></param>
556
/// <param name="sn"></param>
557
/// <param name="path"></param>
558
/// <param name="isDir"></param>
559
void DoShallowNode(DirNode parent, ShallowNode sn, string path, bool isDir)
564
string name = Path.GetFileName(path);
566
// don't let temp files from sync, into the collection as regular nodes
567
if (isSyncFile(name))
570
// If the lastwritetime has not changed the node is up to date.
571
if (File.GetLastWriteTime(path) <= lastDredgeTime)
574
DoSubtree(path, null, sn.ID, false);
578
// If the case of the names does not match we have a conflict.
583
CreateDirNode(path, parent, true);
587
CreateFileNode(path, parent, true);
592
node = Node.NodeFactory(collection, sn);
595
// This is a directory.
596
dn = node as DirNode;
599
// This node is the wrong type.
601
dn = CreateDirNode(path, parent, false);
605
DoSubtree(path, dn, dn.ID, true);
610
fn = node as FileNode;
613
ModifyFileNode(path, fn, false);
618
fn = CreateFileNode(path, parent, false);
627
/// <param name="parentNode"></param>
628
/// <param name="path"></param>
629
/// <param name="isDir"></param>
630
void DoNode(DirNode parentNode, string path, bool isDir)
632
string name = Path.GetFileName(path);
634
if (isSyncFile(name))
637
// find if node for this file or dir already exists
639
ShallowNode sn = GetShallowNodeForFile(path, out haveConflict);
642
DoShallowNode(parentNode, sn, path, isDir);
647
/// Checks to see if the current file is a recursive symlink.
649
/// <param name="path">The path of the possible link.</param>
650
/// <returns>true if recursive link</returns>
651
bool IsRecursiveLink(string path)
655
if (Syscall.lstat(path, out stat) == 0)
657
if ((stat.st_mode & FilePermissions.S_IFLNK) != 0)
659
// If the path begins with the link path this is a recursive link.
660
StringBuilder stringBuff = new StringBuilder(1024);
661
if (Syscall.readlink(path, stringBuff, (ulong)stringBuff.Capacity) != -1)
663
string linkPath = stringBuff.ToString();
664
if (!Path.IsPathRooted(linkPath))
666
linkPath = Path.Combine(Path.GetDirectoryName(path), linkPath);
667
linkPath = Path.GetFullPath(linkPath) + "/";
669
// We need to check for link to a link.
670
if (IsRecursiveLink(linkPath))
672
else if (path.StartsWith(linkPath))
678
if ((File.GetAttributes(path) & FileAttributes.ReparsePoint) != 0)
680
// We need to determine if the link is recursive.
681
// Lets check to see if we have any of the directories in our hiarchy.
682
// Strip of our name.
683
string parentPath = Path.GetDirectoryName(path);
684
string[] fsEntries = Directory.GetDirectories(path);
685
foreach (string dPath in fsEntries)
687
// If any of the directory names match my parent path we could be recursive.
688
// Get the name of the child directory.
689
string childDir = Path.GetFileName(dPath);
690
int sIndex = parentPath.IndexOf(childDir);
693
if (parentPath[sIndex -1] == Path.DirectorySeparatorChar
694
&& (((sIndex + childDir.Length) == parentPath.Length)
695
|| parentPath[sIndex + childDir.Length] == Path.DirectorySeparatorChar))
697
// We have a possible recursion problem.
698
// We need to see if the directories are the same
699
// We will do it by creating a child file and checking for existance
700
// in the suspect directory.
701
string suspectFile = Path.Combine(parentPath.Substring(0, sIndex - 1), ".simias.tmp");
702
string localFile = Path.Combine(path, ".simias.tmp");
703
File.Create(localFile).Close();
706
if (File.Exists(suspectFile))
711
File.Delete(localFile);
721
private bool CheckSuspend
723
// Read the limit from the setup
724
//limit = Simias.Client.SimiasSetup.Limit;
725
//log.Debug("Ramesh: Setting the limit to {0}", limit);
742
/// <param name="path"></param>
743
/// <param name="dnode"></param>
744
/// <param name="nodeID"></param>
745
/// <param name="subTreeHasChanged"></param>
746
void DoSubtree(string path, DirNode dnode, string nodeID, bool subTreeHasChanged)
748
if (Simias.Service.Manager.ShuttingDown)
753
//Log.Spew("Dredger processing subtree of path {0}", path);
754
if (!SyncFile.IsNameValid(Path.GetFileName(path)))
756
// This is a name collision this needs to be resolved before
757
// the files can be added.
761
// Make sure we are not a recursive reparse point or symlink
762
if (IsRecursiveLink(path))
765
if (subTreeHasChanged)
767
// A file or directory has been added or deleted from this directory. We need to find it.
768
Hashtable existingNodes = new Hashtable();
769
// Put all the existing nodes in a hashtable to match against the file system.
770
foreach (ShallowNode sn in collection.Search(PropertyTags.Parent, new Relationship(collection.ID, dnode.ID)))
772
if (Simias.Service.Manager.ShuttingDown)
775
existingNodes[sn.Name] = sn;
778
// Look for new and modified files.
779
foreach (string file in Directory.GetFiles(path))
781
if (Simias.Service.Manager.ShuttingDown)
788
string fName = Path.GetFileName(file);
789
ShallowNode sn = (ShallowNode)existingNodes[fName];
792
DoShallowNode(dnode, sn, file, false);
793
existingNodes.Remove(fName);
797
// The file is new create a new file node.
798
CreateFileNode(file, dnode, false);
802
// look for new directories
803
foreach (string dir in Directory.GetDirectories(path))
805
if (Simias.Service.Manager.ShuttingDown)
811
string dName = Path.GetFileName(dir);
812
ShallowNode sn = (ShallowNode)existingNodes[dName];
815
DoShallowNode(dnode, sn, dir, true);
816
existingNodes.Remove(dName);
820
// The directory is new create a new directory node.
821
DirNode newDir = CreateDirNode(dir, dnode, false);
824
// look for deleted files.
825
// All remaining nodes need to be deleted.
826
foreach (ShallowNode sn in existingNodes.Values)
828
if (Simias.Service.Manager.ShuttingDown)
834
DeleteNode(new Node(collection, sn));
839
// Just look for modified files.
840
foreach (string file in Directory.GetFiles(path))
842
if (Simias.Service.Manager.ShuttingDown)
848
if (File.GetLastWriteTime(file) > lastDredgeTime)
851
dnode = collection.GetNodeByID(nodeID) as DirNode;
852
DoNode(dnode, file, false);
856
foreach (string dir in Directory.GetDirectories(path))
858
if (Simias.Service.Manager.ShuttingDown)
864
if (Directory.GetLastWriteTime(dir) > lastDredgeTime)
867
dnode = collection.GetNodeByID(nodeID) as DirNode;
868
DoNode(dnode, dir, true);
873
ShallowNode sn = GetShallowNodeForFile(dir, out haveConflict);
875
DoSubtree(dir, null, sn.ID, false);
878
// This should never happen but if it does recall with the modified true.
879
DoSubtree(path, dnode, nodeID, true);
888
Log.log.Debug(ex, "Failed adding contents of directory {0}", path);
893
/// Dredge the Managed path.
895
/// <param name="path"></param>
896
void DoManagedPath(string path)
898
// DirectoryInfo tmpDi = new DirectoryInfo(path);
900
// merge files from file system to store
901
foreach (string file in Directory.GetFiles(path))
903
if (File.GetLastWriteTime(file) > lastDredgeTime && !isSyncFile(file))
905
// here we are just checking for modified files
906
// Because we create temporary journal files in the store managed area,
907
// we make sure there is a corresponding node before we proceed.
908
Node node = collection.GetNodeByID(Path.GetFileName(file));
909
if ((node != null) && node.IsType(NodeTypes.BaseFileNodeType))
911
BaseFileNode unode = (BaseFileNode)collection.GetNodeByID(Path.GetFileName(file));
914
// Don't allow journal files (or temporary journal files) to be updated from the client.
915
if (!unode.IsType("Journal") &&
916
!unode.IsType(NodeTypes.FileNodeType))
918
DateTime lastWrote = File.GetLastWriteTime(file);
919
DateTime created = File.GetCreationTime(file);
920
if (unode.LastWriteTime != lastWrote)
922
unode.LastWriteTime = lastWrote;
923
unode.CreationTime = created;
924
log.Debug("Updating store file node for {0} {1}", path, file);
925
collection.Commit(unode);
936
/// Dredge the file sytem to find changes.
938
public void CheckForFileChanges()
940
collection.Refresh();
942
if (watcher == null || needToDredge)
945
needToDredge = false;
951
// Make sure the root directory still exists.
952
if (!Directory.Exists(collection.GetRootDirectory().GetFullPath(collection)))
954
collection.Commit(collection.Delete());
958
dredgeTimeStamp = DateTime.Now;
959
fileChangeEntry[] fChanges;
963
fChanges = new fileChangeEntry[changes.Count];
964
changes.Values.CopyTo(fChanges, 0);
967
// Sort these by time that way they will be put in the change log in order.
968
Array.Sort(fChanges);
970
foreach (fileChangeEntry fc in fChanges)
972
if (fc.eArgs.ChangeType == WatcherChangeTypes.Renamed)
974
RenamedEventArgs args = (RenamedEventArgs)fc.eArgs;
975
log.Debug("Queued Event ---- {0}: {1} -> {2}", args.ChangeType, args.OldFullPath, args.FullPath);
979
log.Debug("Queued Event ---- {0}: {1}", fc.eArgs.ChangeType, fc.eArgs.FullPath);
982
string fullName = GetName(fc.eArgs.FullPath);
985
if (fullName == null)
989
changes.Remove(fc.eArgs.FullPath);
994
FileInfo fi = new FileInfo(fullName);
995
isDir = (fi.Attributes & FileAttributes.Directory) > 0;
997
// Make sure that we let modifications settle.
998
TimeSpan tSpan = DateTime.Now - fc.eventTime;
999
if (tSpan.Seconds < fileChangeEntry.settleTime)
1004
if (!isDir && fi.Exists)
1006
tSpan = DateTime.Now - fi.LastWriteTime;
1007
if (tSpan.Seconds < fileChangeEntry.settleTime)
1012
ShallowNode sn = GetShallowNodeForFile(fullName, out haveConflict);
1015
BaseFileNode fn = null;
1019
node = collection.GetNodeByID(sn.ID);
1020
fn = node as BaseFileNode;
1021
dn = node as DirNode;
1022
// Make sure the type is still valid.
1023
if (fi.Exists && ((isDir && fn != null) || (!isDir && dn != null)))
1025
needToDredge = true;
1029
// We have a node update it.
1030
switch (fc.eArgs.ChangeType)
1032
case WatcherChangeTypes.Created:
1033
case WatcherChangeTypes.Changed:
1035
ModifyFileNode(fullName, fn, false);
1037
case WatcherChangeTypes.Deleted:
1040
case WatcherChangeTypes.Renamed:
1042
RenamedEventArgs args = (RenamedEventArgs)fc.eArgs;
1043
oldNames.Remove(args.OldFullPath);
1045
// Remove any name collisions.
1046
if (collection.HasCollisions(node))
1048
if (collection.GetCollisionType(node) == CollisionType.File)
1049
node = Conflict.RemoveNameConflict(collection, node);
1051
// Check for a name conflict.
1052
if (!SyncFile.IsNameValid(args.Name))
1054
// This is a conflict.
1055
node = Conflict.CreateNameConflict(collection, node, fullName);
1059
// Since we are here we have a node already.
1060
// Make sure the case of the names has not changed.
1061
if (Path.GetFileName(fullName) == node.Name)
1063
// This is a rename back to the original name update it.
1065
ModifyFileNode(fullName, fn, false);
1067
DoSubtree(fullName, dn, node.ID, true);
1071
// This is a case rename.
1074
node = RenameFileNode(fullName, fn);
1078
node = RenameDirNode(fullName, dn);
1082
// Make sure that there is not a node for the old name.
1083
sn = GetShallowNodeForFile(args.OldFullPath, out haveConflict);
1084
if (sn != null && sn.ID != node.ID)
1086
// If the file no longer exists delet the node.
1087
if (!File.Exists(args.OldFullPath))
1089
node = collection.GetNodeByID(sn.ID);
1099
// The node does not exist.
1100
switch (fc.eArgs.ChangeType)
1102
case WatcherChangeTypes.Deleted:
1103
// The node does not exist just continue.
1105
case WatcherChangeTypes.Created:
1106
case WatcherChangeTypes.Changed:
1107
// The node does not exist create it.
1110
CreateDirNode(fullName, GetParentNode(fullName), haveConflict);
1114
CreateFileNode(fullName, GetParentNode(fullName), haveConflict);
1118
case WatcherChangeTypes.Renamed:
1119
// Check if there is a node for the old name.
1120
// Get the node from the old name.
1121
RenamedEventArgs args = (RenamedEventArgs)fc.eArgs;
1122
oldNames.Remove(args.OldFullPath);
1123
DirNode parent = null;
1124
sn = GetShallowNodeForFile(args.OldFullPath, out haveConflict);
1127
node = collection.GetNodeByID(sn.ID);
1128
fn = node as FileNode;
1129
dn = node as DirNode;
1131
// Remove any name collisions.
1132
if (collection.HasCollisions(node))
1134
if (collection.GetCollisionType(node) == CollisionType.File)
1135
node = Conflict.RemoveNameConflict(collection, node);
1137
// Check for a name conflict.
1138
if (!SyncFile.IsNameValid(Path.GetFileName(fullName)))
1140
// This is a conflict.
1141
node = Conflict.CreateNameConflict(collection, node, fullName);
1144
// Make sure the parent has not changed.
1145
if (HasParentChanged(args.OldFullPath, fullName))
1147
// We have a new parent find the parent node.
1148
parent = GetParentNode(fullName);
1151
// We have a parent reset the parent node.
1152
node.Properties.ModifyNodeProperty(PropertyTags.Parent, new Relationship(collection.ID, parent.ID));
1156
// We do not have a node for the parent.
1158
needToDredge = true;
1164
node = RenameFileNode(fullName, node as BaseFileNode);
1168
node = RenameDirNode(fullName, node as DirNode);
1173
// The node does not exist create it.
1174
haveConflict = sn == null ? false : true;
1178
tempNode = CreateDirNode(fullName, GetParentNode(fullName), haveConflict);
1182
tempNode = CreateFileNode(fullName, GetParentNode(fullName), haveConflict);
1190
changes.Remove(fc.eArgs.FullPath);
1197
needToDredge = false;
1201
DoManagedPath(collection.ManagedPath);
1207
needToDredge = false;
1212
// We may have just created or deleted nodes wait for the events to settle.
1213
// We will wait for 2 seconds because of file time resolution on fat32
1214
// This will ensure that we don't miss any changes.
1220
/// Find File changes by dredging the file system.
1222
public void Dredge()
1224
// Clear the event changes since we are going to dredge.
1230
collection.Refresh();
1231
foundChange = false;
1235
lastDredgeTime = (DateTime)(collection.Properties.GetSingleProperty(lastDredgeProp).Value);
1239
// Set found change so the lastDredgeTime will get updated.
1241
log.Debug("Failed to get the last dredge time");
1243
// Make sure that the RootDir still exists. IF it has been deleted on a slave remove the collection
1245
DirNode dn = collection.GetRootDirectory();
1248
rootPath = dn.Properties.GetSingleProperty(PropertyTags.Root).Value as string;
1249
string path = dn.GetFullPath(collection);
1250
if (onServer || Directory.Exists(path))
1252
DoSubtree(path, dn, dn.ID, Directory.GetLastWriteTime(path) > lastDredgeTime ? true : false);
1256
// The directory no loger exits. Delete the collection.
1257
collection.Delete();
1258
collection.Commit();
1259
foundChange = false;
1263
DoManagedPath(collection.ManagedPath);
1267
Property tsp = new Property(lastDredgeProp, dredgeTimeStamp);
1268
tsp.LocalProperty = true;
1269
collection.Properties.ModifyProperty(tsp);
1270
collection.Properties.State = PropertyList.PropertyListState.Internal;
1271
collection.Commit(collection);
1285
private string GetName(string fullPath)
1287
if (MyEnvironment.Windows)
1291
string path = rootPath;
1292
// We need to get the name with case preserved.
1293
string relPath = fullPath.Replace(rootPath, "");
1294
relPath = relPath.TrimStart(Path.DirectorySeparatorChar);
1295
string[] pathComponents = relPath.Split(Path.DirectorySeparatorChar);
1296
foreach (string pc in pathComponents)
1298
//string[] caseSensitivePath = Directory.GetFileSystemEntries(Path.GetDirectoryName(fullPath), Path.GetFileName(fullPath));
1299
string[] caseSensitivePath = Directory.GetFileSystemEntries(path, pc);
1300
if (caseSensitivePath.Length == 1)
1302
// We should only have one match.
1303
path = Path.Combine(path, Path.GetFileName(caseSensitivePath[0]));
1307
// We didn't find the component return the passed in name.
1316
// If this is a sync generated file return null.
1317
if (isSyncFile(fullPath))
1323
private void OnChanged(object source, FileSystemEventArgs e)
1325
string fullPath = e.FullPath;
1327
log.Debug("Changed ---- {0}", e.FullPath);
1328
if (isSyncFile(e.Name))
1333
fileChangeEntry entry = (fileChangeEntry)changes[fullPath];
1336
// This file has already been modified.
1337
// Combine the state.
1338
switch (entry.eArgs.ChangeType)
1340
case WatcherChangeTypes.Created:
1341
case WatcherChangeTypes.Deleted:
1342
case WatcherChangeTypes.Changed:
1345
case WatcherChangeTypes.Renamed:
1352
changes[fullPath] = new fileChangeEntry(e);
1357
private void OnRenamed(object source, RenamedEventArgs e)
1359
string fullPath = e.FullPath;
1360
log.Debug("Renamed ---- {0} -> {1}", e.OldFullPath, e.FullPath);
1362
if (isSyncFile(e.Name) || isSyncFile(e.OldName))
1367
// Any changes made to the old file need to be removed.
1368
changes.Remove(e.OldFullPath);
1369
changes[fullPath] = new fileChangeEntry(e);
1370
oldNames[e.OldFullPath] = e.FullPath;
1371
// If We have an oldName of the new name this is a rename back.
1372
// Create the node for the original rename.
1373
string createName = (string)oldNames[e.FullPath];
1374
if (createName != null)
1376
oldNames.Remove(e.FullPath);
1378
if (File.Exists(createName))
1380
changes[createName] = new fileChangeEntry(
1381
new FileSystemEventArgs(
1382
WatcherChangeTypes.Created, Path.GetDirectoryName(createName), Path.GetFileName(createName)));
1388
private void OnDeleted(object source, FileSystemEventArgs e)
1390
string fullPath = e.FullPath;
1391
log.Debug("Deleted ---- {0}", e.FullPath);
1393
if (isSyncFile(e.Name))
1398
// Check to see if we had a pending rename.
1399
// If so we may need to delete both the new and old
1401
fileChangeEntry entry = (fileChangeEntry)changes[fullPath];
1402
changes[fullPath] = new fileChangeEntry(e);
1403
if (entry != null && entry.eArgs.ChangeType == WatcherChangeTypes.Renamed)
1405
RenamedEventArgs args = entry.eArgs as RenamedEventArgs;
1406
if (!changes.Contains(args.OldFullPath))
1408
// We do not have a file by the old name delete it.
1409
changes[args.OldFullPath] = new fileChangeEntry(
1410
new FileSystemEventArgs(
1411
WatcherChangeTypes.Deleted, Path.GetDirectoryName(args.OldFullPath), args.OldName));
1413
// Now remove the old name entry.
1414
oldNames.Remove(args.OldFullPath);
1419
private void OnCreated(object source, FileSystemEventArgs e)
1421
string fullPath = e.FullPath;
1422
log.Debug("Created ---- {0}", e.FullPath);
1424
if (isSyncFile(e.Name))
1429
changes[fullPath] = new fileChangeEntry(e);
1433
private void watcher_Error(object sender, ErrorEventArgs e)
1435
// We have lost events. we need to dredge.
1436
needToDredge = true;
1439
private void Dispose(bool inFinalize)
1447
System.GC.SuppressFinalize(this);
1449
if (watcher != null)
1458
#region IDisposable Members
1461
/// Called to cleanup unmanaged resources.
1463
public void Dispose()