Skip to content

Commit 28070ac

Browse files
committed
v1.0.0.3019-Alpha
1 parent 9c94e51 commit 28070ac

22 files changed

+915
-264
lines changed

VirtualFileSystem/CustomData.cs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
using ITHit.FileSystem.Windows;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Text;
6+
7+
namespace VirtualFileSystem
8+
{
9+
/// <summary>
10+
/// Custom data stored with a file or folder placeholder, such ETag and original file/folder path. Max 4KB.
11+
/// </summary>
12+
/// <remarks>To avoid storing metatadata and keep footprit small, this class is is using custom serialization.</remarks>
13+
internal class CustomData
14+
{
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+
21+
/// <summary>
22+
/// Keeps the original file/folder path. Used to sync file/folder from user file system to remote storage
23+
/// if this app was not running when the file/folder was moved or renamed. This field allows to avoid
24+
/// delete-create sequence during client to server synchronization after app failure.
25+
/// </summary>
26+
internal string OriginalPath = "";
27+
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+
37+
/// <summary>
38+
/// Serializes all custom data fields into the byte array.
39+
/// </summary>
40+
/// <returns>Byte array representing custom data.</returns>
41+
internal byte[] Serialize()
42+
{
43+
using (MemoryStream m = new MemoryStream())
44+
{
45+
using (BinaryWriter writer = new BinaryWriter(m))
46+
{
47+
writer.Write(ETag);
48+
writer.Write(OriginalPath);
49+
writer.Write(SavedData.Length);
50+
writer.Write(SavedData);
51+
}
52+
return m.ToArray();
53+
}
54+
}
55+
56+
/// <summary>
57+
/// Deserializes custom data from byte array into object.
58+
/// </summary>
59+
/// <param name="data">Byte array representing custom data.</param>
60+
/// <returns></returns>
61+
internal static CustomData Desserialize(byte[] data)
62+
{
63+
if(data == null)
64+
{
65+
throw new ArgumentNullException("data");
66+
}
67+
68+
CustomData obj = new CustomData();
69+
using (MemoryStream m = new MemoryStream(data))
70+
{
71+
using (BinaryReader reader = new BinaryReader(m))
72+
{
73+
obj.ETag = reader.ReadString();
74+
obj.OriginalPath = reader.ReadString();
75+
obj.SavedData = reader.ReadBytes(reader.ReadInt32());
76+
}
77+
}
78+
return obj;
79+
}
80+
}
81+
82+
/// <summary>
83+
/// Placeholder methods to get and set custom data associated with a placeholder, such as ETah and OriginalPath.
84+
/// </summary>
85+
internal static class PlaceholderItemExtensions
86+
{
87+
public static void SetCustomData(this PlaceholderItem placeholder, string eTag, string originalPath)
88+
{
89+
CustomData customData = new CustomData { ETag = eTag, OriginalPath = originalPath };
90+
placeholder.SetCustomData(customData.Serialize());
91+
}
92+
93+
public static void SetCustomData(Microsoft.Win32.SafeHandles.SafeFileHandle safeHandle, string eTag, string originalPath)
94+
{
95+
CustomData customData = new CustomData { ETag = eTag, OriginalPath = originalPath };
96+
PlaceholderItem.SetCustomData(safeHandle, customData.Serialize());
97+
}
98+
99+
public static void SetOriginalPath(this PlaceholderItem placeholder, string originalPath)
100+
{
101+
byte[] customDataRaw = placeholder.GetCustomData();
102+
CustomData customData = (customDataRaw.Length > 0) ? CustomData.Desserialize(customDataRaw) : new CustomData();
103+
104+
customData.OriginalPath = originalPath;
105+
placeholder.SetCustomData(customData.Serialize());
106+
}
107+
108+
public static string GetOriginalPath(this PlaceholderItem placeholder)
109+
{
110+
byte[] customDataRaw = placeholder.GetCustomData();
111+
CustomData customData = (customDataRaw.Length > 0) ? CustomData.Desserialize(customDataRaw) : new CustomData();
112+
return customData.OriginalPath;
113+
}
114+
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+
147+
/// <summary>
148+
/// Returns true if the file was moved in the user file system and changes not yet synched to the remote storage.
149+
/// </summary>
150+
public static bool IsMoved(this PlaceholderItem placeholder)
151+
{
152+
// If original path was never set, the file was just created, not moved.
153+
string originalPath = placeholder.GetOriginalPath();
154+
if (string.IsNullOrEmpty(originalPath))
155+
{
156+
return false;
157+
}
158+
159+
// Otherwise verify that current file path and original file path are equal.
160+
return !originalPath.Equals(placeholder.Path, StringComparison.InvariantCultureIgnoreCase);
161+
}
162+
163+
}
164+
}

VirtualFileSystem/FsPath.cs

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,17 +119,76 @@ public static string GetAttString(string path)
119119
/// <param name="path">Path to a file or folder.</param>
120120
public static bool IsMsOfficeTemp(string path)
121121
{
122-
return Path.GetFileName(path).StartsWith('~') && Path.GetExtension(path).Equals(".tmp", StringComparison.InvariantCultureIgnoreCase);
122+
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
123124
}
124125

126+
/// <summary>
127+
/// Returns true if file system contains MS Office lock file (~$file.ext) in file
128+
/// system that corresponds to the provided path to MS Office file.
129+
/// </summary>
130+
/// <param name="path">Path to MS Office file.</param>
125131
public static bool IsMsOfficeLocked(string path)
126132
{
127-
//string lockFileName = $"~${Path.GetFileName(path)}";
128-
//string lockPath = Path.Combine(Path.GetDirectoryName(path), lockFileName);
133+
string lockPath = GetLockPathFromMsOfficePath(path);
134+
return lockPath != null;
135+
}
136+
137+
138+
/// <summary>
139+
/// Returns true if the provided path points to MS Office lock file (~$file.ext).
140+
/// </summary>
141+
/// <param name="path">Path to lock file.</param>
142+
public static bool IsMsOfficeLockFile(string path)
143+
{
144+
return Path.GetFileName(path).StartsWith("~$");
145+
}
146+
147+
/*
148+
public static string GetMsOfficePathFromLock(string msOfficeLockFilePath)
149+
{
150+
int separatorIndex = msOfficeLockFilePath.LastIndexOf(Path.DirectorySeparatorChar);
151+
return msOfficeLockFilePath.Remove(separatorIndex + 1, "~$".Length);
152+
}
153+
*/
129154

130-
int separatorIndex = path.LastIndexOf(Path.DirectorySeparatorChar);
131-
string lockPath = path.Insert(separatorIndex + 1, "~$");
132-
return File.Exists(lockPath);
155+
/// <summary>
156+
/// Returns MS Office lock file path if such file exists.
157+
/// </summary>
158+
/// <param name="msOfficeFilePath">MS Office file path.</param>
159+
/// <returns>Lock file path.</returns>
160+
/// <remarks>
161+
/// mydoc.docx -> ~$mydoc.docx
162+
/// mydocfi.docx -> ~$ydocfi.docx
163+
/// mydocfile.docx -> ~$docfile.docx
164+
/// mydocfile.pptx -> ~$mydocfile.pptx
165+
/// mydocfile.ppt -> ~$mydocfile.ppt
166+
/// mydocfile.xlsx -> ~$mydocfile.xlsx
167+
/// mydocfile.xls -> null
168+
/// </remarks>
169+
public static string GetLockPathFromMsOfficePath(string msOfficeFilePath)
170+
{
171+
string msOfficeLockFilePath = null;
172+
int separatorIndex = msOfficeFilePath.LastIndexOf(Path.DirectorySeparatorChar);
173+
if ((separatorIndex != -1) && !string.IsNullOrEmpty(Path.GetExtension(msOfficeFilePath)))
174+
{
175+
msOfficeLockFilePath = msOfficeFilePath.Insert(separatorIndex + 1, "~$");
176+
if(FsPath.Exists(msOfficeLockFilePath))
177+
{
178+
return msOfficeLockFilePath;
179+
}
180+
int fileNameLength = Path.GetFileNameWithoutExtension(msOfficeFilePath).Length;
181+
if (fileNameLength > 6)
182+
{
183+
int removeChars = fileNameLength == 7 ? 1 : 2;
184+
msOfficeLockFilePath = msOfficeLockFilePath.Remove(separatorIndex + 1 + "~$".Length, removeChars);
185+
if (FsPath.Exists(msOfficeLockFilePath))
186+
{
187+
return msOfficeLockFilePath;
188+
}
189+
}
190+
}
191+
return null;
133192
}
134193

135194
/// <summary>
@@ -159,7 +218,7 @@ public static bool IsHiddenOrTemp(string path)
159218
/// <param name="path">Path to a file or folder.</param>
160219
public static bool AvoidSync(string path)
161220
{
162-
return IsMsOfficeLocked(path) || IsMsOfficeTemp(path) || IsHiddenOrTemp(path);
221+
return IsMsOfficeLockFile(path) || IsMsOfficeLocked(path) || IsMsOfficeTemp(path) || IsHiddenOrTemp(path);
163222
}
164223

165224
/// <summary>

VirtualFileSystem/Images/Down.ico

108 KB
Binary file not shown.

VirtualFileSystem/Images/Drive.ico

120 KB
Binary file not shown.

VirtualFileSystem/Images/Error.ico

109 KB
Binary file not shown.
109 KB
Binary file not shown.
109 KB
Binary file not shown.

VirtualFileSystem/Images/Up.ico

108 KB
Binary file not shown.
109 KB
Binary file not shown.

VirtualFileSystem/Mapping.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,14 @@ public static FileSystemItemBasicInfo GetUserFileSysteItemInfo(FileSystemInfo re
7171

7272
// Here you will typically store the file ETag. You will send the ETag to
7373
// the server inside If-Match header togater with updated content from client.
74-
// This will make sure the file on the server is not modified.
74+
// This will make sure the changes on the server is not overwritten.
7575
//
76-
// In this sample, for the sake of simplicity, we use file last write time.
77-
userFileSystemItem.CustomData = BitConverter.GetBytes(remoteStorageItem.LastWriteTime.ToBinary());
76+
// In this sample, for the sake of simplicity, we use file last write time instead of ETag.
77+
userFileSystemItem.CustomData = new CustomData
78+
{
79+
ETag = remoteStorageItem.LastWriteTime.ToBinary().ToString(),
80+
OriginalPath = Mapping.ReverseMapPath(remoteStorageItem.FullName)
81+
}.Serialize();
7882

7983
if (remoteStorageItem is FileInfo)
8084
{

0 commit comments

Comments
 (0)