Skip to content

Commit 55423be

Browse files
committed
v1.1.3573.0-Beta
1 parent 28070ac commit 55423be

37 files changed

+1958
-1225
lines changed

README.md

Lines changed: 43 additions & 31 deletions
Large diffs are not rendered by default.
Lines changed: 24 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,26 @@
1-
using ITHit.FileSystem.Windows;
1+
using ITHit.FileSystem;
2+
using ITHit.FileSystem.Windows;
23
using System;
34
using System.Collections.Generic;
45
using System.IO;
56
using System.Text;
7+
using System.Threading.Tasks;
68

79
namespace VirtualFileSystem
810
{
911
/// <summary>
10-
/// Custom data stored with a file or folder placeholder, such ETag and original file/folder path. Max 4KB.
12+
/// Custom data stored with a file or folder placeholder, such original file/folder path. Max 4KB.
1113
/// </summary>
1214
/// <remarks>To avoid storing metatadata and keep footprit small, this class is is using custom serialization.</remarks>
1315
internal class CustomData
1416
{
15-
/// <summary>
16-
/// File ETag. Used to verify that the file on the server is not modified during client to server synchronization.
17-
/// </summary>
18-
/// <remarks>This field is required if the server does not provide locking capabilities.</remarks>
19-
internal string ETag = "";
20-
2117
/// <summary>
2218
/// Keeps the original file/folder path. Used to sync file/folder from user file system to remote storage
2319
/// if this app was not running when the file/folder was moved or renamed. This field allows to avoid
2420
/// delete-create sequence during client to server synchronization after app failure.
2521
/// </summary>
2622
internal string OriginalPath = "";
2723

28-
/// <summary>
29-
/// Used for Microsoft Office lock files (~$file.ext) to store custom data during transactional save.
30-
/// The original MS Office file is renamed and than deleted. As a result the ETag is lost and we can not
31-
/// send the ETag to the server when saving the file.
32-
/// As a solution, we copy custom data from the original file during lock file creation into this field.
33-
/// When the original file is being saved, we read ETag from the lock file.
34-
/// </summary>
35-
internal byte[] SavedData = new byte[] { };
36-
3724
/// <summary>
3825
/// Serializes all custom data fields into the byte array.
3926
/// </summary>
@@ -44,10 +31,7 @@ internal byte[] Serialize()
4431
{
4532
using (BinaryWriter writer = new BinaryWriter(m))
4633
{
47-
writer.Write(ETag);
4834
writer.Write(OriginalPath);
49-
writer.Write(SavedData.Length);
50-
writer.Write(SavedData);
5135
}
5236
return m.ToArray();
5337
}
@@ -70,29 +54,27 @@ internal static CustomData Desserialize(byte[] data)
7054
{
7155
using (BinaryReader reader = new BinaryReader(m))
7256
{
73-
obj.ETag = reader.ReadString();
7457
obj.OriginalPath = reader.ReadString();
75-
obj.SavedData = reader.ReadBytes(reader.ReadInt32());
7658
}
7759
}
7860
return obj;
7961
}
8062
}
8163

8264
/// <summary>
83-
/// Placeholder methods to get and set custom data associated with a placeholder, such as ETah and OriginalPath.
65+
/// Placeholder methods to get and set custom data associated with a placeholder, such as OriginalPath.
8466
/// </summary>
8567
internal static class PlaceholderItemExtensions
8668
{
87-
public static void SetCustomData(this PlaceholderItem placeholder, string eTag, string originalPath)
69+
public static void SetCustomData(this PlaceholderItem placeholder, string originalPath)
8870
{
89-
CustomData customData = new CustomData { ETag = eTag, OriginalPath = originalPath };
71+
CustomData customData = new CustomData { OriginalPath = originalPath };
9072
placeholder.SetCustomData(customData.Serialize());
9173
}
9274

93-
public static void SetCustomData(Microsoft.Win32.SafeHandles.SafeFileHandle safeHandle, string eTag, string originalPath)
75+
public static void SetCustomData(Microsoft.Win32.SafeHandles.SafeFileHandle safeHandle, string originalPath)
9476
{
95-
CustomData customData = new CustomData { ETag = eTag, OriginalPath = originalPath };
77+
CustomData customData = new CustomData { OriginalPath = originalPath };
9678
PlaceholderItem.SetCustomData(safeHandle, customData.Serialize());
9779
}
9880

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

115-
public static void SetETag(this PlaceholderItem placeholder, string eTag)
116-
{
117-
byte[] customDataRaw = placeholder.GetCustomData();
118-
CustomData customData = (customDataRaw.Length > 0) ? CustomData.Desserialize(customDataRaw) : new CustomData();
119-
120-
customData.ETag = eTag;
121-
placeholder.SetCustomData(customData.Serialize());
122-
}
123-
124-
public static string GetETag(this PlaceholderItem placeholder)
125-
{
126-
byte[] customDataRaw = placeholder.GetCustomData();
127-
CustomData customData = (customDataRaw.Length > 0) ? CustomData.Desserialize(customDataRaw) : new CustomData();
128-
return customData.ETag;
129-
}
130-
131-
public static void SetSavedData(this PlaceholderItem placeholder, byte[] saveData)
132-
{
133-
byte[] customDataRaw = placeholder.GetCustomData();
134-
CustomData customData = (customDataRaw.Length > 0) ? CustomData.Desserialize(customDataRaw) : new CustomData();
135-
136-
customData.SavedData = saveData;
137-
placeholder.SetCustomData(customData.Serialize());
138-
}
139-
140-
public static byte[] GetSavedData(this PlaceholderItem placeholder)
141-
{
142-
byte[] customDataRaw = placeholder.GetCustomData();
143-
CustomData customData = (customDataRaw.Length > 0) ? CustomData.Desserialize(customDataRaw) : new CustomData();
144-
return customData.SavedData;
145-
}
146-
14797
/// <summary>
14898
/// Returns true if the file was moved in the user file system and changes not yet synched to the remote storage.
14999
/// </summary>
@@ -160,5 +110,20 @@ public static bool IsMoved(this PlaceholderItem placeholder)
160110
return !originalPath.Equals(placeholder.Path, StringComparison.InvariantCultureIgnoreCase);
161111
}
162112

113+
public static bool IsNew(this PlaceholderItem placeholder)
114+
{
115+
// ETag presence signals if the item is new.
116+
// However, ETag file may not exists during move operation,
117+
// additionally checking OriginalPath presence.
118+
// Can not rely on OriginalPath only,
119+
// because MS Office transactional save deletes and recreates the file.
120+
121+
string originalPath = placeholder.GetOriginalPath();
122+
123+
bool eTagFileExists = File.Exists(ETag.GetETagFilePath(placeholder.Path));
124+
125+
return !eTagFileExists && string.IsNullOrEmpty(originalPath);
126+
}
127+
163128
}
164129
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace VirtualFileSystem
8+
{
9+
/// <summary>
10+
/// Provides method for reading and writing ETags.
11+
/// </summary>
12+
internal static class ETag
13+
{
14+
/// <summary>
15+
/// Creates or updates ETag associated with the file.
16+
/// </summary>
17+
/// <param name="userFileSystemPath">Path in the user file system.</param>
18+
/// <param name="eTag">ETag.</param>
19+
/// <returns></returns>
20+
public static async Task SetETagAsync(string userFileSystemPath, string eTag)
21+
{
22+
string eTagFilePath = GetETagFilePath(userFileSystemPath);
23+
Directory.CreateDirectory(Path.GetDirectoryName(eTagFilePath));
24+
await File.WriteAllTextAsync(eTagFilePath, eTag);
25+
}
26+
27+
/// <summary>
28+
/// Gets ETag associated with a file.
29+
/// </summary>
30+
/// <param name="userFileSystemPath">Path in the user file system.</param>
31+
/// <returns>ETag.</returns>
32+
public static async Task<string> GetETagAsync(string userFileSystemPath)
33+
{
34+
string eTagFilePath = GetETagFilePath(userFileSystemPath);
35+
if (!File.Exists(eTagFilePath))
36+
{
37+
return null;
38+
}
39+
return await File.ReadAllTextAsync(eTagFilePath);
40+
}
41+
42+
/// <summary>
43+
/// Deletes ETag associated with a file.
44+
/// </summary>
45+
/// <param name="userFileSystemSourcePath">Path in the user file system.</param>
46+
public static void DeleteETag(string userFileSystemSourcePath)
47+
{
48+
File.Delete(GetETagFilePath(userFileSystemSourcePath));
49+
}
50+
51+
/// <summary>
52+
/// Gets path to the file in which ETag is stored based on the provided user file system path.
53+
/// </summary>
54+
/// <param name="userFileSystemPath">Path to the file or folder to get the ETag file path.</param>
55+
/// <returns>Path to the file that contains ETag.</returns>
56+
public static string GetETagFilePath(string userFileSystemPath)
57+
{
58+
// Get path relative to the virtual root.
59+
string relativePath = Path.TrimEndingDirectorySeparator(userFileSystemPath).Substring(
60+
Path.TrimEndingDirectorySeparator(Program.Settings.UserFileSystemRootPath).Length);
61+
62+
string path = $"{Path.TrimEndingDirectorySeparator(Program.Settings.ServerDataFolderPath)}{relativePath}.etag";
63+
return path;
64+
}
65+
66+
/// <summary>
67+
/// Returns true if the remote storage ETag and user file system ETags are equal. False - otherwise.
68+
/// </summary>
69+
/// <param name="userFileSystemPath">User file system item.</param>
70+
/// <param name="remoteStorageItem">Remote storage item info.</param>
71+
/// <remarks>
72+
/// ETag is updated on the server during every document update and is sent to client with a file.
73+
/// During client->server update it is sent back to the remote storage together with a modified content.
74+
/// This ensures the changes on the server are not overwritten if the document on the server is modified.
75+
/// </remarks>
76+
internal static async Task<bool> ETagEqualsAsync(string userFileSystemPath, FileSystemItemBasicInfo remoteStorageItem)
77+
{
78+
string remoteStorageETag = remoteStorageItem.ETag;
79+
80+
// Intstead of the real ETag we store remote storage LastWriteTime when
81+
// creating and updating files/folders.
82+
string userFileSystemETag = await ETag.GetETagAsync(userFileSystemPath);
83+
if (string.IsNullOrEmpty(userFileSystemETag))
84+
{
85+
// No ETag associated with the file. This is a new file created in user file system.
86+
return false;
87+
}
88+
89+
return remoteStorageETag == userFileSystemETag;
90+
}
91+
}
92+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using ITHit.FileSystem;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Text;
5+
6+
namespace VirtualFileSystem
7+
{
8+
///<inheritdoc cref="IFileBasicInfo"/>
9+
internal class FileBasicInfo : FileSystemItemBasicInfo, IFileBasicInfo
10+
{
11+
///<inheritdoc/>
12+
public long Length { get; set; }
13+
}
14+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using ITHit.FileSystem;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Text;
6+
7+
namespace VirtualFileSystem
8+
{
9+
/// <summary>
10+
/// Represents a basic information about the file or the folder in the user file system.
11+
/// In addition to properties provided by <see cref="IFileSystemItem"/> this class contains Etag property.
12+
/// </summary>
13+
internal class FileSystemItemBasicInfo : IFileSystemItemBasicInfo
14+
{
15+
///<inheritdoc/>
16+
public string Name { get; set; }
17+
18+
///<inheritdoc/>
19+
public FileAttributes Attributes { get; set; }
20+
21+
///<inheritdoc/>
22+
public byte[] CustomData { get; set; }
23+
24+
///<inheritdoc/>
25+
public DateTime CreationTime { get; set; }
26+
27+
///<inheritdoc/>
28+
public DateTime LastWriteTime { get; set; }
29+
30+
///<inheritdoc/>
31+
public DateTime LastAccessTime { get; set; }
32+
33+
///<inheritdoc/>
34+
public DateTime ChangeTime { get; set; }
35+
36+
/// <summary>
37+
/// Server ETag.
38+
/// </summary>
39+
public string ETag { get; set; }
40+
}
41+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using ITHit.FileSystem;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Text;
5+
6+
namespace VirtualFileSystem
7+
{
8+
/// <inheritdoc cref="IFolderBasicInfo"/>
9+
internal class FolderBasicInfo : FileSystemItemBasicInfo, IFolderBasicInfo
10+
{
11+
12+
}
13+
}

VirtualFileSystem/FsPath.cs renamed to VirtualFileSystem/Framework/FsPath.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,18 +117,19 @@ public static string GetAttString(string path)
117117
/// Returns true if the file hase a format ~XXXXX.tmp, false - otherwise.
118118
/// </summary>
119119
/// <param name="path">Path to a file or folder.</param>
120-
public static bool IsMsOfficeTemp(string path)
120+
private static bool IsMsOfficeTemp(string path)
121121
{
122122
return (Path.GetFileName(path).StartsWith('~') && Path.GetExtension(path).Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)) // Word temp files
123-
|| (Path.GetFileName(path).StartsWith("ppt") && Path.GetExtension(path).Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)); // PowerPoint temp files
123+
|| (Path.GetFileName(path).StartsWith("ppt") && Path.GetExtension(path).Equals(".tmp", StringComparison.InvariantCultureIgnoreCase)) // PowerPoint temp files
124+
|| (string.IsNullOrEmpty(Path.GetExtension(path)) && (Path.GetFileName(path).Length == 8)); // Excel temp files
124125
}
125126

126127
/// <summary>
127128
/// Returns true if file system contains MS Office lock file (~$file.ext) in file
128129
/// system that corresponds to the provided path to MS Office file.
129130
/// </summary>
130131
/// <param name="path">Path to MS Office file.</param>
131-
public static bool IsMsOfficeLocked(string path)
132+
private static bool IsMsOfficeLocked(string path)
132133
{
133134
string lockPath = GetLockPathFromMsOfficePath(path);
134135
return lockPath != null;
@@ -139,7 +140,7 @@ public static bool IsMsOfficeLocked(string path)
139140
/// Returns true if the provided path points to MS Office lock file (~$file.ext).
140141
/// </summary>
141142
/// <param name="path">Path to lock file.</param>
142-
public static bool IsMsOfficeLockFile(string path)
143+
private static bool IsMsOfficeLockFile(string path)
143144
{
144145
return Path.GetFileName(path).StartsWith("~$");
145146
}
@@ -166,7 +167,7 @@ public static string GetMsOfficePathFromLock(string msOfficeLockFilePath)
166167
/// mydocfile.xlsx -> ~$mydocfile.xlsx
167168
/// mydocfile.xls -> null
168169
/// </remarks>
169-
public static string GetLockPathFromMsOfficePath(string msOfficeFilePath)
170+
private static string GetLockPathFromMsOfficePath(string msOfficeFilePath)
170171
{
171172
string msOfficeLockFilePath = null;
172173
int separatorIndex = msOfficeFilePath.LastIndexOf(Path.DirectorySeparatorChar);
@@ -199,7 +200,7 @@ public static string GetLockPathFromMsOfficePath(string msOfficeFilePath)
199200
/// True if the file or folder is marked with Hidden or Temporaty attributes.
200201
/// Returns false if no Hidden or Temporaty attributes found or file/folder does not exists.
201202
/// </returns>
202-
public static bool IsHiddenOrTemp(string path)
203+
private static bool IsHiddenOrTemp(string path)
203204
{
204205
if(!FsPath.Exists(path))
205206
{

0 commit comments

Comments
 (0)