Skip to content

Commit

Permalink
v1.1.3573.0-Beta
Browse files Browse the repository at this point in the history
  • Loading branch information
ITHitBuild committed Oct 22, 2020
1 parent 28070ac commit 55423be
Show file tree
Hide file tree
Showing 37 changed files with 1,958 additions and 1,225 deletions.
74 changes: 43 additions & 31 deletions README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,39 +1,26 @@
using ITHit.FileSystem.Windows;
using ITHit.FileSystem;
using ITHit.FileSystem.Windows;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace VirtualFileSystem
{
/// <summary>
/// Custom data stored with a file or folder placeholder, such ETag and original file/folder path. Max 4KB.
/// Custom data stored with a file or folder placeholder, such original file/folder path. Max 4KB.
/// </summary>
/// <remarks>To avoid storing metatadata and keep footprit small, this class is is using custom serialization.</remarks>
internal class CustomData
{
/// <summary>
/// File ETag. Used to verify that the file on the server is not modified during client to server synchronization.
/// </summary>
/// <remarks>This field is required if the server does not provide locking capabilities.</remarks>
internal string ETag = "";

/// <summary>
/// Keeps the original file/folder path. Used to sync file/folder from user file system to remote storage
/// if this app was not running when the file/folder was moved or renamed. This field allows to avoid
/// delete-create sequence during client to server synchronization after app failure.
/// </summary>
internal string OriginalPath = "";

/// <summary>
/// Used for Microsoft Office lock files (~$file.ext) to store custom data during transactional save.
/// The original MS Office file is renamed and than deleted. As a result the ETag is lost and we can not
/// send the ETag to the server when saving the file.
/// As a solution, we copy custom data from the original file during lock file creation into this field.
/// When the original file is being saved, we read ETag from the lock file.
/// </summary>
internal byte[] SavedData = new byte[] { };

/// <summary>
/// Serializes all custom data fields into the byte array.
/// </summary>
Expand All @@ -44,10 +31,7 @@ internal byte[] Serialize()
{
using (BinaryWriter writer = new BinaryWriter(m))
{
writer.Write(ETag);
writer.Write(OriginalPath);
writer.Write(SavedData.Length);
writer.Write(SavedData);
}
return m.ToArray();
}
Expand All @@ -70,29 +54,27 @@ internal static CustomData Desserialize(byte[] data)
{
using (BinaryReader reader = new BinaryReader(m))
{
obj.ETag = reader.ReadString();
obj.OriginalPath = reader.ReadString();
obj.SavedData = reader.ReadBytes(reader.ReadInt32());
}
}
return obj;
}
}

/// <summary>
/// Placeholder methods to get and set custom data associated with a placeholder, such as ETah and OriginalPath.
/// Placeholder methods to get and set custom data associated with a placeholder, such as OriginalPath.
/// </summary>
internal static class PlaceholderItemExtensions
{
public static void SetCustomData(this PlaceholderItem placeholder, string eTag, string originalPath)
public static void SetCustomData(this PlaceholderItem placeholder, string originalPath)
{
CustomData customData = new CustomData { ETag = eTag, OriginalPath = originalPath };
CustomData customData = new CustomData { OriginalPath = originalPath };
placeholder.SetCustomData(customData.Serialize());
}

public static void SetCustomData(Microsoft.Win32.SafeHandles.SafeFileHandle safeHandle, string eTag, string originalPath)
public static void SetCustomData(Microsoft.Win32.SafeHandles.SafeFileHandle safeHandle, string originalPath)
{
CustomData customData = new CustomData { ETag = eTag, OriginalPath = originalPath };
CustomData customData = new CustomData { OriginalPath = originalPath };
PlaceholderItem.SetCustomData(safeHandle, customData.Serialize());
}

Expand All @@ -112,38 +94,6 @@ public static string GetOriginalPath(this PlaceholderItem placeholder)
return customData.OriginalPath;
}

public static void SetETag(this PlaceholderItem placeholder, string eTag)
{
byte[] customDataRaw = placeholder.GetCustomData();
CustomData customData = (customDataRaw.Length > 0) ? CustomData.Desserialize(customDataRaw) : new CustomData();

customData.ETag = eTag;
placeholder.SetCustomData(customData.Serialize());
}

public static string GetETag(this PlaceholderItem placeholder)
{
byte[] customDataRaw = placeholder.GetCustomData();
CustomData customData = (customDataRaw.Length > 0) ? CustomData.Desserialize(customDataRaw) : new CustomData();
return customData.ETag;
}

public static void SetSavedData(this PlaceholderItem placeholder, byte[] saveData)
{
byte[] customDataRaw = placeholder.GetCustomData();
CustomData customData = (customDataRaw.Length > 0) ? CustomData.Desserialize(customDataRaw) : new CustomData();

customData.SavedData = saveData;
placeholder.SetCustomData(customData.Serialize());
}

public static byte[] GetSavedData(this PlaceholderItem placeholder)
{
byte[] customDataRaw = placeholder.GetCustomData();
CustomData customData = (customDataRaw.Length > 0) ? CustomData.Desserialize(customDataRaw) : new CustomData();
return customData.SavedData;
}

/// <summary>
/// Returns true if the file was moved in the user file system and changes not yet synched to the remote storage.
/// </summary>
Expand All @@ -160,5 +110,20 @@ public static bool IsMoved(this PlaceholderItem placeholder)
return !originalPath.Equals(placeholder.Path, StringComparison.InvariantCultureIgnoreCase);
}

public static bool IsNew(this PlaceholderItem placeholder)
{
// ETag presence signals if the item is new.
// However, ETag file may not exists during move operation,
// additionally checking OriginalPath presence.
// Can not rely on OriginalPath only,
// because MS Office transactional save deletes and recreates the file.

string originalPath = placeholder.GetOriginalPath();

bool eTagFileExists = File.Exists(ETag.GetETagFilePath(placeholder.Path));

return !eTagFileExists && string.IsNullOrEmpty(originalPath);
}

}
}
92 changes: 92 additions & 0 deletions VirtualFileSystem/Framework/ETag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace VirtualFileSystem
{
/// <summary>
/// Provides method for reading and writing ETags.
/// </summary>
internal static class ETag
{
/// <summary>
/// Creates or updates ETag associated with the file.
/// </summary>
/// <param name="userFileSystemPath">Path in the user file system.</param>
/// <param name="eTag">ETag.</param>
/// <returns></returns>
public static async Task SetETagAsync(string userFileSystemPath, string eTag)
{
string eTagFilePath = GetETagFilePath(userFileSystemPath);
Directory.CreateDirectory(Path.GetDirectoryName(eTagFilePath));
await File.WriteAllTextAsync(eTagFilePath, eTag);
}

/// <summary>
/// Gets ETag associated with a file.
/// </summary>
/// <param name="userFileSystemPath">Path in the user file system.</param>
/// <returns>ETag.</returns>
public static async Task<string> GetETagAsync(string userFileSystemPath)
{
string eTagFilePath = GetETagFilePath(userFileSystemPath);
if (!File.Exists(eTagFilePath))
{
return null;
}
return await File.ReadAllTextAsync(eTagFilePath);
}

/// <summary>
/// Deletes ETag associated with a file.
/// </summary>
/// <param name="userFileSystemSourcePath">Path in the user file system.</param>
public static void DeleteETag(string userFileSystemSourcePath)
{
File.Delete(GetETagFilePath(userFileSystemSourcePath));
}

/// <summary>
/// Gets path to the file in which ETag is stored based on the provided user file system path.
/// </summary>
/// <param name="userFileSystemPath">Path to the file or folder to get the ETag file path.</param>
/// <returns>Path to the file that contains ETag.</returns>
public static string GetETagFilePath(string userFileSystemPath)
{
// Get path relative to the virtual root.
string relativePath = Path.TrimEndingDirectorySeparator(userFileSystemPath).Substring(
Path.TrimEndingDirectorySeparator(Program.Settings.UserFileSystemRootPath).Length);

string path = $"{Path.TrimEndingDirectorySeparator(Program.Settings.ServerDataFolderPath)}{relativePath}.etag";
return path;
}

/// <summary>
/// Returns true if the remote storage ETag and user file system ETags are equal. False - otherwise.
/// </summary>
/// <param name="userFileSystemPath">User file system item.</param>
/// <param name="remoteStorageItem">Remote storage item info.</param>
/// <remarks>
/// ETag is updated on the server during every document update and is sent to client with a file.
/// During client->server update it is sent back to the remote storage together with a modified content.
/// This ensures the changes on the server are not overwritten if the document on the server is modified.
/// </remarks>
internal static async Task<bool> ETagEqualsAsync(string userFileSystemPath, FileSystemItemBasicInfo remoteStorageItem)
{
string remoteStorageETag = remoteStorageItem.ETag;

// Intstead of the real ETag we store remote storage LastWriteTime when
// creating and updating files/folders.
string userFileSystemETag = await ETag.GetETagAsync(userFileSystemPath);
if (string.IsNullOrEmpty(userFileSystemETag))
{
// No ETag associated with the file. This is a new file created in user file system.
return false;
}

return remoteStorageETag == userFileSystemETag;
}
}
}
14 changes: 14 additions & 0 deletions VirtualFileSystem/Framework/FileBasicInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using ITHit.FileSystem;
using System;
using System.Collections.Generic;
using System.Text;

namespace VirtualFileSystem
{
///<inheritdoc cref="IFileBasicInfo"/>
internal class FileBasicInfo : FileSystemItemBasicInfo, IFileBasicInfo
{
///<inheritdoc/>
public long Length { get; set; }
}
}
41 changes: 41 additions & 0 deletions VirtualFileSystem/Framework/FileSystemItemBasicInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using ITHit.FileSystem;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace VirtualFileSystem
{
/// <summary>
/// Represents a basic information about the file or the folder in the user file system.
/// In addition to properties provided by <see cref="IFileSystemItem"/> this class contains Etag property.
/// </summary>
internal class FileSystemItemBasicInfo : IFileSystemItemBasicInfo
{
///<inheritdoc/>
public string Name { get; set; }

///<inheritdoc/>
public FileAttributes Attributes { get; set; }

///<inheritdoc/>
public byte[] CustomData { get; set; }

///<inheritdoc/>
public DateTime CreationTime { get; set; }

///<inheritdoc/>
public DateTime LastWriteTime { get; set; }

///<inheritdoc/>
public DateTime LastAccessTime { get; set; }

///<inheritdoc/>
public DateTime ChangeTime { get; set; }

/// <summary>
/// Server ETag.
/// </summary>
public string ETag { get; set; }
}
}
13 changes: 13 additions & 0 deletions VirtualFileSystem/Framework/FolderBasicInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using ITHit.FileSystem;
using System;
using System.Collections.Generic;
using System.Text;

namespace VirtualFileSystem
{
/// <inheritdoc cref="IFolderBasicInfo"/>
internal class FolderBasicInfo : FileSystemItemBasicInfo, IFolderBasicInfo
{

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,19 @@ public static string GetAttString(string path)
/// Returns true if the file hase a format ~XXXXX.tmp, false - otherwise.
/// </summary>
/// <param name="path">Path to a file or folder.</param>
public static bool IsMsOfficeTemp(string path)
private static bool IsMsOfficeTemp(string path)
{
return (Path.GetFileName(path).StartsWith('~') && Path.GetExtension(path).Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)) // Word temp files
|| (Path.GetFileName(path).StartsWith("ppt") && Path.GetExtension(path).Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)); // PowerPoint temp files
|| (Path.GetFileName(path).StartsWith("ppt") && Path.GetExtension(path).Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)) // PowerPoint temp files
|| (string.IsNullOrEmpty(Path.GetExtension(path)) && (Path.GetFileName(path).Length == 8)); // Excel temp files
}

/// <summary>
/// Returns true if file system contains MS Office lock file (~$file.ext) in file
/// system that corresponds to the provided path to MS Office file.
/// </summary>
/// <param name="path">Path to MS Office file.</param>
public static bool IsMsOfficeLocked(string path)
private static bool IsMsOfficeLocked(string path)
{
string lockPath = GetLockPathFromMsOfficePath(path);
return lockPath != null;
Expand All @@ -139,7 +140,7 @@ public static bool IsMsOfficeLocked(string path)
/// Returns true if the provided path points to MS Office lock file (~$file.ext).
/// </summary>
/// <param name="path">Path to lock file.</param>
public static bool IsMsOfficeLockFile(string path)
private static bool IsMsOfficeLockFile(string path)
{
return Path.GetFileName(path).StartsWith("~$");
}
Expand All @@ -166,7 +167,7 @@ public static string GetMsOfficePathFromLock(string msOfficeLockFilePath)
/// mydocfile.xlsx -> ~$mydocfile.xlsx
/// mydocfile.xls -> null
/// </remarks>
public static string GetLockPathFromMsOfficePath(string msOfficeFilePath)
private static string GetLockPathFromMsOfficePath(string msOfficeFilePath)
{
string msOfficeLockFilePath = null;
int separatorIndex = msOfficeFilePath.LastIndexOf(Path.DirectorySeparatorChar);
Expand Down Expand Up @@ -199,7 +200,7 @@ public static string GetLockPathFromMsOfficePath(string msOfficeFilePath)
/// True if the file or folder is marked with Hidden or Temporaty attributes.
/// Returns false if no Hidden or Temporaty attributes found or file/folder does not exists.
/// </returns>
public static bool IsHiddenOrTemp(string path)
private static bool IsHiddenOrTemp(string path)
{
if(!FsPath.Exists(path))
{
Expand Down
Loading

0 comments on commit 55423be

Please sign in to comment.