Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions GVFS/GVFS.Common/FileBasedCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public abstract class FileBasedCollection : IDisposable

private const string AddEntryPrefix = "A ";
private const string RemoveEntryPrefix = "D ";

// Use the same newline separator regardless of platform
private const string NewLine = "\r\n";
private const int IoFailureRetryDelayMS = 50;
private const int IoFailureLoggingThreshold = 500;

Expand Down Expand Up @@ -390,7 +393,7 @@ private void WriteToDisk(string value)
throw new InvalidOperationException(nameof(this.WriteToDisk) + " requires that collectionAppendsDirectlyToFile be true");
}

byte[] bytes = Encoding.UTF8.GetBytes(value + "\r\n");
byte[] bytes = Encoding.UTF8.GetBytes(value + NewLine);
lock (this.fileLock)
{
this.dataFileHandle.Write(bytes, 0, bytes.Length);
Expand All @@ -399,7 +402,7 @@ private void WriteToDisk(string value)
}

/// <summary>
/// Reads entries from dataFileHandle, removing any data after the last \r\n. Requires fileLock.
/// Reads entries from dataFileHandle, removing any data after the last NewLine ("\r\n"). Requires fileLock.
/// </summary>
private void RemoveLastEntryIfInvalid()
{
Expand Down Expand Up @@ -442,7 +445,7 @@ private bool TryWriteTempFile(Func<IEnumerable<string>> getDataLines, out Except
{
foreach (string line in getDataLines())
{
writer.Write(line + "\r\n");
writer.Write(line + NewLine);
}

tempFile.Flush();
Expand Down
2 changes: 1 addition & 1 deletion GVFS/GVFS.Common/FileSystem/IKernelDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ namespace GVFS.Common.FileSystem
{
public interface IKernelDriver
{
bool EnumerationExpandsDirectories { get; }
string DriverLogFolderName { get; }

bool IsSupported(string normalizedEnlistmentRootPath, out string warning, out string error);
string FlushDriverLogs();
bool TryPrepareFolderForCallbacks(string folderPath, out string error);
Expand Down
112 changes: 92 additions & 20 deletions GVFS/GVFS.Common/PlaceholderListDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ namespace GVFS.Common
{
public class PlaceholderListDatabase : FileBasedCollection
{
// Special folder values must:
// - Be 40 characters long
// - Not be a valid SHA-1 value (to avoid collisions with files)
public const string PartialFolderValue = " PARTIAL FOLDER";
public const string ExpandedFolderValue = " EXPANDED FOLDER";

private const char PathTerminator = '\0';

// This list holds entries that would otherwise be lost because WriteAllEntriesAndFlush has not been called, but a file
Expand Down Expand Up @@ -51,19 +57,29 @@ public static bool TryCreate(ITracer tracer, string dataFilePath, PhysicalFileSy
output = temp;
return true;
}

public void AddAndFlush(string path, string sha)

public void AddAndFlushFile(string path, string sha)
{
this.AddAndFlush(path, sha);
}

public void AddAndFlushFolder(string path, bool isExpanded)
{
this.AddAndFlush(path, isExpanded ? ExpandedFolderValue : PartialFolderValue);
}

public void RemoveAndFlush(string path)
{
try
{
this.WriteAddEntry(
path + PathTerminator + sha,
this.WriteRemoveEntry(
path,
() =>
{
this.EstimatedCount++;
this.EstimatedCount--;
if (this.placeholderDataEntries != null)
{
this.placeholderDataEntries.Add(new PlaceholderDataEntry(path, sha));
this.placeholderDataEntries.Add(new PlaceholderDataEntry(path));
}
});
}
Expand All @@ -73,38 +89,61 @@ public void AddAndFlush(string path, string sha)
}
}

public void RemoveAndFlush(string path)
public List<PlaceholderData> GetAllEntries()
{
try
{
this.WriteRemoveEntry(
path,
List<PlaceholderData> placeholders = new List<PlaceholderData>(Math.Max(1, this.EstimatedCount));

string error;
if (!this.TryLoadFromDisk<string, string>(
this.TryParseAddLine,
this.TryParseRemoveLine,
(key, value) => placeholders.Add(new PlaceholderData(path: key, fileShaOrFolderValue: value)),
out error,
() =>
{
this.EstimatedCount--;
if (this.placeholderDataEntries != null)
{
this.placeholderDataEntries.Add(new PlaceholderDataEntry(path));
throw new InvalidOperationException("PlaceholderListDatabase should always flush queue placeholders using WriteAllEntriesAndFlush before calling GetAllEntries again.");
}
});

this.placeholderDataEntries = new List<PlaceholderDataEntry>();
}))
{
throw new InvalidDataException(error);
}

return placeholders;
}
catch (Exception e)
{
throw new FileBasedCollectionException(e);
}
}

public List<PlaceholderData> GetAllEntries()
public void GetAllEntries(out List<PlaceholderData> filePlaceholders, out List<PlaceholderData> folderPlaceholders)
{
try
{
List<PlaceholderData> output = new List<PlaceholderData>(Math.Max(1, this.EstimatedCount));
List<PlaceholderData> filePlaceholdersFromDisk = new List<PlaceholderData>(Math.Max(1, this.EstimatedCount));
List<PlaceholderData> folderPlaceholdersFromDisk = new List<PlaceholderData>(Math.Max(1, (int)(this.EstimatedCount * .3)));

string error;
if (!this.TryLoadFromDisk<string, string>(
this.TryParseAddLine,
this.TryParseRemoveLine,
(key, value) => output.Add(new PlaceholderData(path: key, sha: value)),
(key, value) =>
{
if (value == PartialFolderValue || value == ExpandedFolderValue)
{
folderPlaceholdersFromDisk.Add(new PlaceholderData(path: key, fileShaOrFolderValue: value));
}
else
{
filePlaceholdersFromDisk.Add(new PlaceholderData(path: key, fileShaOrFolderValue: value));
}
},
out error,
() =>
{
Expand All @@ -119,7 +158,8 @@ public List<PlaceholderData> GetAllEntries()
throw new InvalidDataException(error);
}

return output;
filePlaceholders = filePlaceholdersFromDisk;
folderPlaceholders = folderPlaceholdersFromDisk;
}
catch (Exception e)
{
Expand Down Expand Up @@ -181,6 +221,27 @@ private IEnumerable<string> GenerateDataLines(IEnumerable<PlaceholderData> updat
}
}

private void AddAndFlush(string path, string sha)
{
try
{
this.WriteAddEntry(
path + PathTerminator + sha,
() =>
{
this.EstimatedCount++;
if (this.placeholderDataEntries != null)
{
this.placeholderDataEntries.Add(new PlaceholderDataEntry(path, sha));
}
});
}
catch (Exception e)
{
throw new FileBasedCollectionException(e);
}
}

private bool TryParseAddLine(string line, out string key, out string value, out string error)
{
// Expected: <Placeholder-Path>\0<40-Char-SHA1>
Expand All @@ -197,7 +258,7 @@ private bool TryParseAddLine(string line, out string key, out string value, out
{
key = null;
value = null;
error = "Invalid SHA1 length: " + line;
error = $"Invalid SHA1 length {line.Length - idx - 1}: " + line;
return false;
}

Expand All @@ -218,18 +279,29 @@ private bool TryParseRemoveLine(string line, out string key, out string error)

public class PlaceholderData
{
public PlaceholderData(string path, string sha)
public PlaceholderData(string path, string fileShaOrFolderValue)
{
this.Path = path;
this.Sha = sha;
this.Sha = fileShaOrFolderValue;
}

public string Path { get; }
public string Sha { get; }

public bool IsFolder
{
get { return this.Sha == GVFSConstants.AllZeroSha; }
get
{
return this.Sha == PartialFolderValue || this.IsExpandedFolder;
}
}

public bool IsExpandedFolder
{
get
{
return this.Sha == ExpandedFolderValue;
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion GVFS/GVFS.Common/RepoMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ public static class DiskLayoutVersion
// The major version should be bumped whenever there is an on-disk format change that requires a one-way upgrade.
// Increasing this version will make older versions of GVFS unable to mount a repo that has been mounted by a newer
// version of GVFS.
public const int CurrentMajorVersion = 16;
public const int CurrentMajorVersion = 17;

// The minor version should be bumped whenever there is an upgrade that can be safely ignored by older versions of GVFS.
// For example, this allows an upgrade step that sets a default value for some new config setting.
Expand Down
Loading