Skip to content

Commit 8576762

Browse files
committed
v1.3.4067.0
1 parent 58d9417 commit 8576762

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4402
-10
lines changed

VirtualFileSystem/Framework/CustomData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public static bool IsMoved(this PlaceholderItem placeholder)
108108
}
109109

110110
// Otherwise verify that current file path and original file path are equal.
111-
return !originalPath.Equals(placeholder.Path, StringComparison.InvariantCultureIgnoreCase);
111+
return !originalPath.TrimEnd(Path.DirectorySeparatorChar).Equals(placeholder.Path.TrimEnd(Path.DirectorySeparatorChar), StringComparison.InvariantCultureIgnoreCase);
112112
}
113113

114114
/// <summary>

VirtualFileSystem/Framework/ETag.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,12 @@ public static string GetETagFilePath(string userFileSystemPath)
7676
internal static async Task<bool> ETagEqualsAsync(string userFileSystemPath, FileSystemItemBasicInfo remoteStorageItem)
7777
{
7878
string remoteStorageETag = remoteStorageItem.ETag;
79-
80-
// Intstead of the real ETag we store remote storage LastWriteTime when
81-
// creating and updating files/folders.
8279
string userFileSystemETag = await ETag.GetETagAsync(userFileSystemPath);
83-
if (string.IsNullOrEmpty(userFileSystemETag))
80+
81+
if (string.IsNullOrEmpty(remoteStorageETag) && string.IsNullOrEmpty(userFileSystemETag))
8482
{
85-
// No ETag associated with the file. This is a new file created in user file system.
86-
return false;
83+
// We assume the remote storage is not using ETags or no ETag is ssociated with this file/folder.
84+
return true;
8785
}
8886

8987
return remoteStorageETag == userFileSystemETag;

VirtualFileSystem/Framework/Syncronyzation/RemoteStorageRawItem.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public static async Task CreateAsync(string userFileSystemNewItemPath, ILogger l
5959
logger.LogMessage("Creating item in the remote storage", userFileSystemNewItemPath);
6060
await new RemoteStorageRawItem(userFileSystemNewItemPath, logger).CreateOrUpdateAsync(FileMode.CreateNew);
6161
await new UserFileSystemRawItem(userFileSystemNewItemPath).ClearStateAsync();
62-
logger.LogMessage("Created item in the remote storage succesefully", userFileSystemNewItemPath);
62+
logger.LogMessage("Created in the remote storage succesefully", userFileSystemNewItemPath);
6363
}
6464
catch (Exception ex)
6565
{
File renamed without changes.

VirtualFileSystem/UserFileSystemItem.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public UserFileSystemItem(string userFileSystemPath, LockInfo lockInfo = null)
4545
/// <summary>
4646
/// Renames or moves file or folder to a new location in the remote storage.
4747
/// </summary>
48-
/// <param name="userFileSystemNewPath">Target path of this file of folder in the user file system.</param>
48+
/// <param name="userFileSystemNewPath">Target path of this file or folder in the user file system.</param>
4949
public async Task MoveToAsync(string userFileSystemNewPath)
5050
{
5151
string remoteStorageOldPath = RemoteStoragePath;

VirtualFileSystem/VirtualFileSystem.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" />
3030
</ItemGroup>
3131
<ItemGroup>
32-
<PackageReference Include="ITHit.FileSystem.Windows" Version="1.2.3984.0" />
32+
<PackageReference Include="ITHit.FileSystem.Windows" Version="1.3.4067.0" />
3333
</ItemGroup>
3434
<ItemGroup>
3535
<None Update="appsettings.json">
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Text;
5+
6+
namespace VirtualFileSystem
7+
{
8+
/// <summary>
9+
/// Thrown when a file can not be locked. For example when a lock-token file is blocked
10+
/// from another thread, during update, lock and unlock operations.
11+
/// </summary>
12+
public class ClientLockFailedException : IOException
13+
{
14+
/// <summary>
15+
/// Creates instance of this class.
16+
/// </summary>
17+
/// <param name="message">The error message that explains the reason for the exception.</param>
18+
public ClientLockFailedException(string message) : base(message)
19+
{
20+
}
21+
22+
/// <summary>
23+
/// Creates instance of this class with a specified
24+
/// error message and a reference to the inner exception that is the cause of this
25+
/// exception.
26+
/// </summary>
27+
/// <param name="message">The error message that explains the reason for the exception.</param>
28+
/// <param name="innerException">
29+
/// The exception that is the cause of the current exception. If the innerException
30+
/// parameter is not null, the current exception is raised in a catch block that
31+
/// handles the inner exception.
32+
/// </param>
33+
public ClientLockFailedException(string message, Exception innerException) : base(message, innerException)
34+
{
35+
}
36+
37+
/// <summary>
38+
/// Creates instance of this class with a message and HRESULT code.
39+
/// </summary>
40+
/// <param name="message">The error message that explains the reason for the exception.</param>
41+
/// <param name="hresult">An integer identifying the error that has occurred.</param>
42+
public ClientLockFailedException(string message, int hresult) : base(message, hresult)
43+
{
44+
}
45+
}
46+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
using ITHit.FileSystem;
2+
using ITHit.FileSystem.Windows;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace VirtualFileSystem
10+
{
11+
/// <summary>
12+
/// Custom data stored with a file or folder placeholder, such original file/folder path. Max 4KB.
13+
/// </summary>
14+
/// <remarks>To avoid storing metatadata and keep footprit small, this class is is using custom serialization.</remarks>
15+
internal class CustomData
16+
{
17+
/// <summary>
18+
/// Keeps the original file/folder path. Used to sync file/folder from user file system to remote storage
19+
/// if this app was not running when the file/folder was moved or renamed. This field allows to avoid
20+
/// delete-create sequence during client to server synchronization after app failure.
21+
/// </summary>
22+
internal string OriginalPath = "";
23+
24+
/// <summary>
25+
/// Serializes all custom data fields into the byte array.
26+
/// </summary>
27+
/// <returns>Byte array representing custom data.</returns>
28+
internal byte[] Serialize()
29+
{
30+
using (MemoryStream m = new MemoryStream())
31+
{
32+
using (BinaryWriter writer = new BinaryWriter(m))
33+
{
34+
writer.Write(OriginalPath);
35+
}
36+
return m.ToArray();
37+
}
38+
}
39+
40+
/// <summary>
41+
/// Deserializes custom data from byte array into object.
42+
/// </summary>
43+
/// <param name="data">Byte array representing custom data.</param>
44+
/// <returns></returns>
45+
internal static CustomData Desserialize(byte[] data)
46+
{
47+
if(data == null)
48+
{
49+
throw new ArgumentNullException("data");
50+
}
51+
52+
CustomData obj = new CustomData();
53+
using (MemoryStream m = new MemoryStream(data))
54+
{
55+
using (BinaryReader reader = new BinaryReader(m))
56+
{
57+
obj.OriginalPath = reader.ReadString();
58+
}
59+
}
60+
return obj;
61+
}
62+
}
63+
64+
/// <summary>
65+
/// Placeholder methods to get and set custom data associated with a placeholder, such as OriginalPath.
66+
/// </summary>
67+
internal static class PlaceholderItemExtensions
68+
{
69+
public static void SetCustomData(this PlaceholderItem placeholder, string originalPath)
70+
{
71+
CustomData customData = new CustomData { OriginalPath = originalPath };
72+
placeholder.SetCustomData(customData.Serialize());
73+
}
74+
75+
public static void SetCustomData(Microsoft.Win32.SafeHandles.SafeFileHandle safeHandle, string originalPath)
76+
{
77+
CustomData customData = new CustomData { OriginalPath = originalPath };
78+
PlaceholderItem.SetCustomData(safeHandle, customData.Serialize());
79+
}
80+
81+
public static void SetOriginalPath(this PlaceholderItem placeholder, string originalPath)
82+
{
83+
byte[] customDataRaw = placeholder.GetCustomData();
84+
CustomData customData = (customDataRaw.Length > 0) ? CustomData.Desserialize(customDataRaw) : new CustomData();
85+
86+
customData.OriginalPath = originalPath;
87+
placeholder.SetCustomData(customData.Serialize());
88+
}
89+
90+
public static string GetOriginalPath(this PlaceholderItem placeholder)
91+
{
92+
byte[] customDataRaw = placeholder.GetCustomData();
93+
CustomData customData = (customDataRaw.Length > 0) ? CustomData.Desserialize(customDataRaw) : new CustomData();
94+
return customData.OriginalPath;
95+
}
96+
97+
/// <summary>
98+
/// Returns true if the file was moved in the user file system and
99+
/// changes not yet synched to the remote storage.
100+
/// </summary>
101+
public static bool IsMoved(this PlaceholderItem placeholder)
102+
{
103+
// If original path was never set, the file was just created, not moved.
104+
string originalPath = placeholder.GetOriginalPath();
105+
if (string.IsNullOrEmpty(originalPath))
106+
{
107+
return false;
108+
}
109+
110+
// Otherwise verify that current file path and original file path are equal.
111+
return !originalPath.TrimEnd(Path.DirectorySeparatorChar).Equals(placeholder.Path.TrimEnd(Path.DirectorySeparatorChar), StringComparison.InvariantCultureIgnoreCase);
112+
}
113+
114+
/// <summary>
115+
/// Returns true if the item was created and must be synched to remote storage.
116+
/// </summary>
117+
/// <returns>
118+
/// True if the item was created in the user file system and does not exists
119+
/// in the remote storage. False otherwise.
120+
/// </returns>
121+
public static bool IsNew(this PlaceholderItem placeholder)
122+
{
123+
// ETag absence signals that the item is new.
124+
// However, ETag file may not exists during move operation,
125+
// additionally checking OriginalPath presence.
126+
// Can not rely on OriginalPath only,
127+
// because MS Office files are being deleted and re-created during transactional save.
128+
129+
string originalPath = placeholder.GetOriginalPath();
130+
131+
bool eTagFileExists = File.Exists(ETag.GetETagFilePath(placeholder.Path));
132+
133+
return !eTagFileExists && string.IsNullOrEmpty(originalPath);
134+
}
135+
136+
}
137+
}

WebDAVDrive/Framework/ETag.cs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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="userFileSystemPath">Path in the user file system.</param>
46+
public static void DeleteETag(string userFileSystemPath)
47+
{
48+
File.Delete(GetETagFilePath(userFileSystemPath));
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+
string userFileSystemETag = await ETag.GetETagAsync(userFileSystemPath);
80+
81+
if (string.IsNullOrEmpty(remoteStorageETag) && string.IsNullOrEmpty(userFileSystemETag))
82+
{
83+
// We assume the remote storage is not using ETags or no ETag is ssociated with this file/folder.
84+
return true;
85+
}
86+
87+
return remoteStorageETag == userFileSystemETag;
88+
}
89+
}
90+
}
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+
}

0 commit comments

Comments
 (0)