mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-17 23:56:05 +02:00
691 lines
21 KiB
Beef
691 lines
21 KiB
Beef
using System;
|
|
using System.Collections;
|
|
using System.Collections;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.IO;
|
|
using Beefy;
|
|
using System.Threading;
|
|
using System.Diagnostics;
|
|
|
|
namespace IDE
|
|
{
|
|
public class FileWatcher
|
|
{
|
|
class WatcherEntry : RefCounted
|
|
{
|
|
// Either mFileSystemWatcher or mParentDirWatcher is set
|
|
public FileSystemWatcher mFileSystemWatcher ~ delete _;
|
|
public WatcherEntry mParentDirWatcher;
|
|
|
|
public String mDirectoryName;
|
|
public bool mIgnoreWrites;
|
|
public bool mHasSubDirDependencies;
|
|
public HashSet<String> mAmbiguousCreatePaths = new .() ~ DeleteContainerAndItems!(_);
|
|
}
|
|
|
|
class DepInfo
|
|
{
|
|
public List<Object> mDependentObjects = new List<Object>() ~ delete _;
|
|
public String mContent ~ delete _;
|
|
}
|
|
|
|
struct QueuedRefreshEntry
|
|
{
|
|
public WatcherEntry mWatcherEntry;
|
|
public int32 mDelay;
|
|
}
|
|
|
|
class QueuedFileChange
|
|
{
|
|
public String mFileName ~ delete _;
|
|
public String mNewFileName ~ delete _;
|
|
public WatcherChangeTypes mChangeType;
|
|
}
|
|
|
|
public class ChangeRecord
|
|
{
|
|
public String mPath ~ delete _;
|
|
public WatcherChangeTypes mChangeType;
|
|
}
|
|
|
|
// One watcher per directory
|
|
public static int32 sDbgFileCreateDelay;
|
|
Dictionary<String, WatcherEntry> mWatchers = new Dictionary<String, WatcherEntry>();
|
|
Dictionary<String, DepInfo> mWatchedFiles = new Dictionary<String, DepInfo>() ~ DeleteDictionaryAndKeysAndValues!(_); // Including ref count
|
|
List<ChangeRecord> mChangeList = new .() ~ DeleteContainerAndItems!(_);
|
|
Dictionary<String, ChangeRecord> mChangeMap = new .() ~ delete _;
|
|
HashSet<void*> mDependencyChangeSet = new .() ~ delete _;
|
|
List<void*> mDependencyChangeList = new .() ~ delete _;
|
|
List<QueuedRefreshEntry> mQueuedRefreshWatcherEntries = new List<QueuedRefreshEntry>() ~ delete _;
|
|
public Monitor mMonitor = new Monitor() ~ delete _;
|
|
List<QueuedFileChange> mQueuedFileChanges = new List<QueuedFileChange>() ~ DeleteContainerAndItems!(_);
|
|
public Monitor mFileChangeMonitor = new Monitor() ~ delete _;
|
|
public int mChangeId;
|
|
|
|
public ~this()
|
|
{
|
|
for (var watcher in mWatchers)
|
|
{
|
|
delete watcher.key;
|
|
watcher.value.DeleteUnchecked(); // Allow even when refCount isn't zero
|
|
}
|
|
delete mWatchers;
|
|
}
|
|
|
|
void FixFilePath(String filePath)
|
|
{
|
|
String oldPath = scope String(filePath);
|
|
filePath.Clear();
|
|
Path.GetFullPath(oldPath, filePath);
|
|
IDEUtils.FixFilePath(filePath);
|
|
if (!Environment.IsFileSystemCaseSensitive)
|
|
filePath.ToUpper();
|
|
}
|
|
|
|
void FileChanged(String filePath, String newPath, WatcherChangeTypes changeType)
|
|
{
|
|
bool isDirectory = filePath.EndsWith(Path.DirectorySeparatorChar);
|
|
|
|
if (!filePath.EndsWith('*'))
|
|
{
|
|
String starPath = Path.GetDirectoryPath(filePath, .. scope .());
|
|
starPath.Append(Path.DirectorySeparatorChar);
|
|
starPath.Append('*');
|
|
FileChanged(starPath, null, .Changed);
|
|
}
|
|
|
|
if ((isDirectory) && (changeType == .Renamed))
|
|
{
|
|
// On Windows, renaming a directory with only case changes will result in a remove before a rename
|
|
var dirName = scope String();
|
|
Path.GetDirectoryPath(newPath.Substring(0, newPath.Length - 1), dirName);
|
|
dirName.Append(Path.DirectorySeparatorChar);
|
|
|
|
FileChanged(dirName, newPath, .DirectoryCreated);
|
|
}
|
|
|
|
var newPath;
|
|
if (isDirectory)
|
|
{
|
|
if ((newPath != null) && (newPath.EndsWith(Path.DirectorySeparatorChar)))
|
|
{
|
|
newPath = scope:: String();
|
|
newPath.Append(@newPath, 0, @newPath.Length - 1);
|
|
}
|
|
}
|
|
|
|
if ((changeType == .Renamed) && (!isDirectory))
|
|
{
|
|
// ALWAYS interpret 'file rename' notifications as a delete of filePath and a create of newPath
|
|
// A manual rename in the IDE will have manually processed the rename before we get here, so
|
|
// we expect 'filePath' won't actually match anything and the 'newPath' will already be set up
|
|
// in the mWatchedFile
|
|
FileChanged(filePath, null, .Deleted);
|
|
|
|
var dirName = scope String();
|
|
Path.GetDirectoryPath(filePath, dirName);
|
|
dirName.Append(Path.DirectorySeparatorChar);
|
|
FileChanged(dirName, newPath, .FileCreated);
|
|
return;
|
|
}
|
|
|
|
if ((changeType == .FileCreated) && (gApp.IsFilteredOut(newPath)))
|
|
return;
|
|
|
|
String fixedFilePath = scope String(filePath);
|
|
FixFilePath(fixedFilePath);
|
|
|
|
bool wantLoadContent = false;
|
|
if (newPath == null)
|
|
{
|
|
using (mMonitor.Enter())
|
|
{
|
|
DepInfo depInfo;
|
|
mWatchedFiles.TryGetValue(fixedFilePath, out depInfo);
|
|
if (depInfo != null)
|
|
wantLoadContent = depInfo.mContent != null;
|
|
}
|
|
}
|
|
|
|
//Debug.WriteLine("FileChanged {0} {1} {2}", filePath, newPath, changeType);
|
|
|
|
String newContent = scope String();
|
|
if (wantLoadContent)
|
|
{
|
|
//TODO: Make this better
|
|
for (int32 i = 0; i < 25; i++)
|
|
{
|
|
if (gApp.LoadTextFile(fixedFilePath, newContent) case .Ok)
|
|
break;
|
|
Thread.Sleep(10);
|
|
}
|
|
}
|
|
|
|
using (mMonitor.Enter())
|
|
{
|
|
DepInfo depInfo;
|
|
mWatchedFiles.TryGetValue(fixedFilePath, out depInfo);
|
|
if (depInfo == null)
|
|
{
|
|
if (changeType == .Deleted)
|
|
{
|
|
fixedFilePath.Append(IDEUtils.cNativeSlash);
|
|
mWatchedFiles.TryGetValue(fixedFilePath, out depInfo);
|
|
}
|
|
|
|
if (depInfo == null)
|
|
return;
|
|
}
|
|
|
|
if (depInfo.mContent != null)
|
|
{
|
|
/*File.WriteAllText(@"c:\\temp\\file1.txt", newContent);
|
|
File.WriteAllText(@"c:\\temp\\file2.txt", depInfo.mContent);*/
|
|
|
|
if (newContent == null) // No actual content (file renamed - as part of file.new, file -> file.old, file.new -> file
|
|
return;
|
|
if ((depInfo.mContent != null) && (Utils.FileTextEquals(depInfo.mContent, newContent)))
|
|
return;
|
|
|
|
// We were only saving this file until the content actually changed
|
|
delete depInfo.mContent;
|
|
depInfo.mContent = null;
|
|
}
|
|
|
|
bool added = mChangeMap.TryAdd(fixedFilePath, var keyPtr, var valuePtr);
|
|
if (added)
|
|
{
|
|
ChangeRecord changeRecord = new .();
|
|
changeRecord.mPath = new String(fixedFilePath);
|
|
changeRecord.mChangeType = changeType;
|
|
*keyPtr = changeRecord.mPath;
|
|
*valuePtr = changeRecord;
|
|
mChangeList.Add(changeRecord);
|
|
}
|
|
else
|
|
{
|
|
let changeRecord = *valuePtr;
|
|
changeRecord.mChangeType |= changeType;
|
|
}
|
|
ProjectItem projectItem = null;
|
|
for (var dep in depInfo.mDependentObjects)
|
|
{
|
|
if (var tryProjectItem = dep as ProjectItem)
|
|
projectItem = tryProjectItem;
|
|
if (dep != null)
|
|
{
|
|
var depPtr = Internal.UnsafeCastToPtr(dep);
|
|
if (mDependencyChangeSet.Add(depPtr))
|
|
mDependencyChangeList.Add(depPtr);
|
|
}
|
|
}
|
|
|
|
if (projectItem != null)
|
|
gApp.OnWatchedFileChanged(projectItem, changeType, newPath);
|
|
}
|
|
}
|
|
|
|
public void OmitFileChange(String filePath, String content = null)
|
|
{
|
|
String fixedFilePath = scope String(filePath);
|
|
FixFilePath(fixedFilePath);
|
|
|
|
using (mMonitor.Enter())
|
|
{
|
|
DepInfo depInfo;
|
|
mWatchedFiles.TryGetValue(fixedFilePath, out depInfo);
|
|
if (depInfo != null)
|
|
{
|
|
String.NewOrSet!(depInfo.mContent, content);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void FileChanged(String filePath)
|
|
{
|
|
FileChanged(filePath, null, WatcherChangeTypes.Changed);
|
|
}
|
|
|
|
void TryAddRefreshWatchEntry(WatcherEntry watcherEntry, int32 delay = 0)
|
|
{
|
|
Debug.Assert(watcherEntry != null);
|
|
Debug.Assert(watcherEntry.mParentDirWatcher == null);
|
|
|
|
bool found = false;
|
|
for (int32 i = 0; i < mQueuedRefreshWatcherEntries.Count; i++)
|
|
{
|
|
var refreshEntry = ref mQueuedRefreshWatcherEntries[i];
|
|
if (refreshEntry.mWatcherEntry == watcherEntry)
|
|
{
|
|
refreshEntry.mDelay = Math.Min(refreshEntry.mDelay, delay);
|
|
found = true;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
QueuedRefreshEntry refreshEntry;
|
|
watcherEntry.AddRef();
|
|
refreshEntry.mWatcherEntry = watcherEntry;
|
|
refreshEntry.mDelay = delay;
|
|
mQueuedRefreshWatcherEntries.Add(refreshEntry);
|
|
}
|
|
}
|
|
|
|
void QueueFileChanged(FileSystemWatcher fileSystemWatcher, String fileName, String newName, WatcherChangeTypes changeType)
|
|
{
|
|
//Debug.WriteLine("QueueFileChanged {0} {1} {2} {3}", fileSystemWatcher.Directory, fileName, newName, changeType);
|
|
|
|
using (mFileChangeMonitor.Enter())
|
|
{
|
|
String fullPath = scope String();
|
|
fullPath.Append(fileSystemWatcher.Directory);
|
|
fullPath.Append(Path.DirectorySeparatorChar);
|
|
if (fileName != null)
|
|
{
|
|
fullPath.Append(fileName);
|
|
}
|
|
|
|
var queuedFileChange = new QueuedFileChange();
|
|
if ((changeType == .FileCreated) || (changeType == .DirectoryCreated))
|
|
{
|
|
queuedFileChange.mFileName = new String();
|
|
Path.GetDirectoryPath(fullPath, queuedFileChange.mFileName);
|
|
queuedFileChange.mFileName.Append(Path.DirectorySeparatorChar);
|
|
queuedFileChange.mNewFileName = new String(fullPath);
|
|
}
|
|
else
|
|
{
|
|
queuedFileChange.mFileName = new String(fullPath);
|
|
if (newName != null)
|
|
{
|
|
var newFullPath = new String();
|
|
newFullPath.Append(fileSystemWatcher.Directory);
|
|
newFullPath.Append(Path.DirectorySeparatorChar);
|
|
newFullPath.Append(newName);
|
|
queuedFileChange.mNewFileName = newFullPath;
|
|
}
|
|
}
|
|
queuedFileChange.mChangeType = changeType;
|
|
mQueuedFileChanges.Add(queuedFileChange);
|
|
mChangeId++;
|
|
}
|
|
}
|
|
|
|
void StartDirectoryWatcher(WatcherEntry watcherEntry)
|
|
{
|
|
FileSystemWatcher fileSystemWatcher = null;
|
|
|
|
//Debug.WriteLine("StartDirectoryWatcher {0}", watcherEntry.mDirectoryName);
|
|
|
|
//Console.WriteLine("StartDirectoryWatcher");
|
|
fileSystemWatcher = new FileSystemWatcher(watcherEntry.mDirectoryName);
|
|
fileSystemWatcher.IncludeSubdirectories = watcherEntry.mHasSubDirDependencies;
|
|
delete watcherEntry.mFileSystemWatcher;
|
|
watcherEntry.mFileSystemWatcher = null;
|
|
|
|
void GetPath(String fileName, String outPath)
|
|
{
|
|
outPath.Append(watcherEntry.mDirectoryName);
|
|
outPath.Append(Path.DirectorySeparatorChar);
|
|
outPath.Append(fileName);
|
|
}
|
|
|
|
void CheckFileCreated(String fileName)
|
|
{
|
|
if (sDbgFileCreateDelay > 0)
|
|
Thread.Sleep(sDbgFileCreateDelay);
|
|
|
|
var filePath = scope String();
|
|
GetPath(fileName, filePath);
|
|
if (File.Exists(filePath))
|
|
{
|
|
QueueFileChanged(fileSystemWatcher, fileName, null, .FileCreated);
|
|
}
|
|
else if (Directory.Exists(filePath))
|
|
QueueFileChanged(fileSystemWatcher, fileName, null, .DirectoryCreated);
|
|
else
|
|
{
|
|
using (mFileChangeMonitor.Enter())
|
|
{
|
|
if (watcherEntry.mAmbiguousCreatePaths.TryAdd(fileName, var entryPtr))
|
|
*entryPtr = new String(fileName);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HasAmbiguousFileName(String fileName)
|
|
{
|
|
switch (watcherEntry.mAmbiguousCreatePaths.GetAndRemove(fileName))
|
|
{
|
|
case .Ok(let str):
|
|
delete str;
|
|
return true;
|
|
case .Err:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!watcherEntry.mIgnoreWrites)
|
|
fileSystemWatcher.OnChanged.Add(new (fileName) =>
|
|
{
|
|
using (mFileChangeMonitor.Enter())
|
|
{
|
|
if (HasAmbiguousFileName(fileName))
|
|
{
|
|
CheckFileCreated(fileName);
|
|
}
|
|
|
|
QueueFileChanged(fileSystemWatcher, fileName, null, .Changed);
|
|
}
|
|
});
|
|
fileSystemWatcher.OnCreated.Add(new (fileName) =>
|
|
{
|
|
CheckFileCreated(fileName);
|
|
});
|
|
fileSystemWatcher.OnDeleted.Add(new (fileName) =>
|
|
{
|
|
using (mFileChangeMonitor.Enter())
|
|
{
|
|
if (HasAmbiguousFileName(fileName))
|
|
{
|
|
// We didn't process the CREATE so just ignore the DELETE
|
|
}
|
|
else
|
|
QueueFileChanged(fileSystemWatcher, fileName, null, .Deleted);
|
|
}
|
|
});
|
|
fileSystemWatcher.OnRenamed.Add(new (oldName, newName) =>
|
|
{
|
|
using (mFileChangeMonitor.Enter())
|
|
{
|
|
if (HasAmbiguousFileName(oldName))
|
|
{
|
|
// We didn't process the CREATE so treat this as a new CREATE
|
|
CheckFileCreated(newName);
|
|
}
|
|
else
|
|
{
|
|
var newFilePath = scope String();
|
|
GetPath(newName, newFilePath);
|
|
|
|
if (Directory.Exists(newFilePath))
|
|
{
|
|
let dirOldName = scope String()..Concat(oldName, Path.DirectorySeparatorChar);
|
|
let dirNewName = scope String()..Concat(newName, Path.DirectorySeparatorChar);
|
|
QueueFileChanged(fileSystemWatcher, dirOldName, dirNewName, .Renamed);
|
|
}
|
|
else
|
|
QueueFileChanged(fileSystemWatcher, oldName, newName, .Renamed);
|
|
}
|
|
}
|
|
});
|
|
fileSystemWatcher.OnError.Add(new () => QueueFileChanged(fileSystemWatcher, null, null, .Failed));
|
|
if (fileSystemWatcher.StartRaisingEvents() case .Err)
|
|
{
|
|
delete fileSystemWatcher;
|
|
//TryAddRefreshWatchEntry(watcherEntry, 30); // Try again later?
|
|
}
|
|
else
|
|
{
|
|
fileSystemWatcher.OnError.Add(new () =>
|
|
{
|
|
if (watcherEntry.mFileSystemWatcher == fileSystemWatcher)
|
|
{
|
|
TryAddRefreshWatchEntry(watcherEntry);
|
|
}
|
|
});
|
|
watcherEntry.mFileSystemWatcher = fileSystemWatcher;
|
|
}
|
|
}
|
|
|
|
// This corrects an error where we attempted to watch a directory that wasn't valid but now it is
|
|
public void FileIsValid(String filePath)
|
|
{
|
|
String fixedFilePath = scope String(filePath);
|
|
FixFilePath(fixedFilePath);
|
|
using (mMonitor.Enter())
|
|
{
|
|
String directoryName = scope String();
|
|
Path.GetDirectoryPath(fixedFilePath, directoryName);
|
|
WatcherEntry watcherEntry;
|
|
mWatchers.TryGetValue(directoryName, out watcherEntry);
|
|
if (watcherEntry != null)
|
|
{
|
|
if ((watcherEntry.mFileSystemWatcher == null) && (watcherEntry.mParentDirWatcher == null))
|
|
{
|
|
TryAddRefreshWatchEntry(watcherEntry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void WatchFile(String filePath, Object dependentObject = null, bool ignoreWrites = false)
|
|
{
|
|
//Debug.WriteLine("WatchFile {0}", filePath);
|
|
|
|
#if !CLI
|
|
String fixedFilePath = scope String(filePath);
|
|
FixFilePath(fixedFilePath);
|
|
using (mMonitor.Enter())
|
|
{
|
|
DepInfo depInfo;
|
|
mWatchedFiles.TryGetValue(fixedFilePath, out depInfo);
|
|
if (depInfo != null)
|
|
{
|
|
depInfo.mDependentObjects.Add(dependentObject);
|
|
return;
|
|
}
|
|
depInfo = new DepInfo();
|
|
depInfo.mDependentObjects.Add(dependentObject);
|
|
mWatchedFiles[new String(fixedFilePath)] = depInfo;
|
|
|
|
String directoryName = scope String();
|
|
Path.GetDirectoryPath(fixedFilePath, directoryName);
|
|
|
|
mWatchers.TryGetValue(directoryName, var watcherEntry);
|
|
if (watcherEntry != null)
|
|
{
|
|
watcherEntry.AddRef();
|
|
}
|
|
else
|
|
{
|
|
directoryName = new String(directoryName);
|
|
|
|
watcherEntry = new WatcherEntry();
|
|
watcherEntry.mDirectoryName = directoryName;
|
|
watcherEntry.mIgnoreWrites = ignoreWrites;
|
|
|
|
String checkDir = scope String()..Append(directoryName);
|
|
while (true)
|
|
{
|
|
String parentDirName = scope String();
|
|
if (Path.GetDirectoryPath(checkDir, parentDirName) case .Err)
|
|
break;
|
|
if (mWatchers.TryGetValue(parentDirName, var parentWatcherEntry))
|
|
{
|
|
while (parentWatcherEntry.mParentDirWatcher != null)
|
|
parentWatcherEntry = parentWatcherEntry.mParentDirWatcher;
|
|
|
|
parentWatcherEntry.AddRef();
|
|
watcherEntry.mParentDirWatcher = parentWatcherEntry;
|
|
if (!parentWatcherEntry.mHasSubDirDependencies)
|
|
{
|
|
// Restart the watcher with the IncludeSubdirectories flag
|
|
parentWatcherEntry.mHasSubDirDependencies = true;
|
|
StartDirectoryWatcher(parentWatcherEntry);
|
|
}
|
|
break;
|
|
}
|
|
|
|
checkDir.Set(parentDirName);
|
|
}
|
|
|
|
if (watcherEntry.mParentDirWatcher == null)
|
|
StartDirectoryWatcher(watcherEntry);
|
|
mWatchers[directoryName] = watcherEntry;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
public void DerefWatcherEntry(WatcherEntry watchEntry)
|
|
{
|
|
if (watchEntry.ReleaseRefNoDelete() == 0)
|
|
{
|
|
if (mWatchers.GetAndRemove(watchEntry.mDirectoryName) case .Ok((var key, var value)))
|
|
{
|
|
if (watchEntry.mParentDirWatcher != null)
|
|
DerefWatcherEntry(watchEntry.mParentDirWatcher);
|
|
|
|
Debug.Assert(watchEntry == value);
|
|
delete key;
|
|
delete watchEntry;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RemoveWatch(String filePath, Object dependentObject = null)
|
|
{
|
|
#if !CLI
|
|
String fixedFilePath = scope String(filePath);
|
|
FixFilePath(fixedFilePath);
|
|
using (mMonitor.Enter())
|
|
{
|
|
DepInfo depInfo;
|
|
String outKey;
|
|
if (!mWatchedFiles.TryGet(fixedFilePath, out outKey, out depInfo))
|
|
return;
|
|
if (dependentObject != null)
|
|
depInfo.mDependentObjects.Remove(dependentObject);
|
|
|
|
if (depInfo.mDependentObjects.Count == 0)
|
|
{
|
|
mWatchedFiles.Remove(fixedFilePath);
|
|
|
|
String directoryName = scope String();
|
|
Path.GetDirectoryPath(fixedFilePath, directoryName);
|
|
WatcherEntry watcherEntry = null;
|
|
String key;
|
|
mWatchers.TryGet(directoryName, out key, out watcherEntry);
|
|
|
|
DerefWatcherEntry(watcherEntry);
|
|
|
|
delete outKey;
|
|
delete depInfo;
|
|
}
|
|
|
|
if (dependentObject != null)
|
|
mDependencyChangeSet.Remove(Internal.UnsafeCastToPtr(dependentObject));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
public void Update(delegate void(String, String, WatcherChangeTypes) fileChangeHandler = null)
|
|
{
|
|
while (true)
|
|
{
|
|
QueuedFileChange queuedFileChange;
|
|
using (mFileChangeMonitor.Enter())
|
|
{
|
|
if (mQueuedFileChanges.Count == 0)
|
|
break;
|
|
queuedFileChange = mQueuedFileChanges.PopFront();
|
|
}
|
|
|
|
if (fileChangeHandler != null)
|
|
fileChangeHandler(queuedFileChange.mFileName, queuedFileChange.mNewFileName, queuedFileChange.mChangeType);
|
|
|
|
FileChanged(queuedFileChange.mFileName, queuedFileChange.mNewFileName, queuedFileChange.mChangeType);
|
|
delete queuedFileChange;
|
|
}
|
|
|
|
using (mMonitor.Enter())
|
|
{
|
|
if (mQueuedRefreshWatcherEntries.Count == 0)
|
|
return;
|
|
ref QueuedRefreshEntry refreshEntry = ref mQueuedRefreshWatcherEntries[0];
|
|
Debug.Assert(refreshEntry.mWatcherEntry != null);
|
|
if (refreshEntry.mDelay > 0)
|
|
{
|
|
refreshEntry.mDelay--;
|
|
}
|
|
else
|
|
{
|
|
StartDirectoryWatcher(refreshEntry.mWatcherEntry);
|
|
DerefWatcherEntry(refreshEntry.mWatcherEntry);
|
|
mQueuedRefreshWatcherEntries.RemoveAt(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
public ChangeRecord PopChangedFile()
|
|
{
|
|
using (mMonitor.Enter())
|
|
{
|
|
if (mChangeList.Count == 0)
|
|
return null;
|
|
let changeRecord = mChangeList[0];
|
|
bool removed = mChangeMap.Remove(changeRecord.mPath);
|
|
Debug.Assert(removed);
|
|
mChangeList.RemoveAt(0);
|
|
return changeRecord;
|
|
}
|
|
}
|
|
|
|
public void AddChangedFile(ChangeRecord changeRecord)
|
|
{
|
|
using (mMonitor.Enter())
|
|
{
|
|
bool added = mChangeMap.TryAdd(changeRecord.mPath, var keyPtr, var valuePtr);
|
|
if (added)
|
|
{
|
|
*keyPtr = changeRecord.mPath;
|
|
*valuePtr = changeRecord;
|
|
mChangeList.Add(changeRecord);
|
|
}
|
|
else
|
|
{
|
|
delete changeRecord;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RemoveChangedFile(String str)
|
|
{
|
|
using (mMonitor.Enter())
|
|
{
|
|
if (mChangeMap.GetAndRemove(str) case .Ok(let kv))
|
|
{
|
|
mChangeList.Remove(kv.value);
|
|
delete kv.value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public Object PopChangedDependency()
|
|
{
|
|
using (mMonitor.Enter())
|
|
{
|
|
while (true)
|
|
{
|
|
if (mDependencyChangeList.IsEmpty)
|
|
return null;
|
|
var dep = mDependencyChangeList.PopFront();
|
|
if (mDependencyChangeSet.Remove(dep))
|
|
return Internal.UnsafeCastToObject(dep);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void AddChangedDependency(Object obj)
|
|
{
|
|
using (mMonitor.Enter())
|
|
{
|
|
var depPtr = Internal.UnsafeCastToPtr(obj);
|
|
if (mDependencyChangeSet.Add(depPtr))
|
|
mDependencyChangeList.Add(depPtr);
|
|
}
|
|
}
|
|
}
|
|
}
|