Skip to content

Commit 44bf230

Browse files
committed
While scanning directories, handle files with invalid names (for example, on a network drive) gracefully: do not abort the directory scan, and also return the file name with invalid characters. Some file names that .NET considered as invalid during dirlist, are actually accessible by Windows API. Added some other reliability updates as well.
1 parent 4c05eb1 commit 44bf230

31 files changed

+6454
-494
lines changed

.gitignore

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
/bin
33
/obj
44
/packages
5-
/*.bak
6-
/FolderSyncNet.csproj.user
5+
*.bak
6+
/*.csproj.user
77
/UpgradeLog.htm
8+
*.*~
9+
10+
.vshistory

App.config

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
55
</startup>
66
<runtime>
7+
<ThrowUnobservedTaskExceptions enabled="false"/>
78
<!-- https://docs.microsoft.com/en-us/archive/blogs/jeremykuhne/net-4-6-2-and-long-paths-on-windows-10 -->
89
<AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" />
910
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

BinaryFileExtensions.cs

+348-65
Large diffs are not rendered by default.

CachedFileInfo.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (c) Roland Pihlakas 2019 - 2022
2+
// Copyright (c) Roland Pihlakas 2019 - 2023
33
44
//
55
// Roland Pihlakas licenses this file to you under the GNU Lesser General Public License, ver 2.1.
@@ -74,5 +74,5 @@ public CachedFileInfo(FileSystemInfo fileSystemInfo)
7474
FullName = fileSystemInfo.FullName;
7575
Attributes = fileSystemInfo.Attributes;
7676
}
77-
} //private class CachedFileInfo : ISerializable
77+
} //private class CachedFileInfo
7878
}

Extensions.cs

+303-57
Large diffs are not rendered by default.

FileExtensions.cs

+43-13
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ private static async Task InternalWriteAllTextAsync(StreamWriter sw, string cont
6565
//sw.WriteAsync(buffer, 0, batchSize).ConfigureAwait(false);
6666
await Extensions.FSOperation
6767
(
68-
async () => await sw.WriteAsync(buffer, 0, batchSize), //TODO: add cancellationToken here?
68+
async (cancellationAndTimeoutToken) => await sw.WriteAsync(buffer, 0, batchSize), //TODO: add cancellationToken here?
6969
path,
7070
cancellationToken,
7171
timeout: timeout ?? Global.FileBufferWriteTimeout,
@@ -77,17 +77,36 @@ await Extensions.FSOperation
7777
}
7878

7979
//cancellationToken.ThrowIfCancellationRequested(); //cob roland: let log writing complete
80-
await Extensions.FSOperation
81-
(
82-
async () => await sw.FlushAsync(), //TODO: add cancellationToken here?
83-
path,
84-
//cancellationToken, //cob roland: let log writing complete
85-
default(CancellationToken), //roland: let log writing complete
86-
timeout: timeout ?? Global.FileBufferWriteTimeout,
87-
suppressLogFile: suppressLogFile,
88-
suppressLongRunningOperationMessage: suppressLongRunningOperationMessage
89-
);
90-
cancellationToken.ThrowIfCancellationRequested(); //roland: let log writing complete
80+
/*do //roland
81+
{
82+
try
83+
{*/
84+
await Extensions.FSOperation
85+
(
86+
async (cancellationAndTimeoutToken) => await sw.FlushAsync(), //TODO: add cancellationToken here?
87+
path,
88+
//cancellationToken, //cob roland: let log writing complete
89+
default(CancellationToken), //roland: let log writing complete
90+
timeout: timeout ?? Global.FileBufferWriteTimeout,
91+
suppressLogFile: suppressLogFile,
92+
suppressLongRunningOperationMessage: suppressLongRunningOperationMessage
93+
);
94+
95+
/*break;
96+
}
97+
catch (Exception)
98+
{
99+
//TODO: tune delay
100+
#if !NOASYNC
101+
await Task.Delay(10, cancellationToken);
102+
#else
103+
cancellationToken.WaitHandle.WaitOne(10);
104+
#endif
105+
}
106+
}
107+
while (!cancellationToken.IsCancellationRequested);*/
108+
109+
cancellationToken.ThrowIfCancellationRequested(); //roland: let log writing complete at least for one attempt loop
91110
}
92111
finally
93112
{
@@ -118,6 +137,7 @@ await Extensions.FSOperation
118137
throw new ArgumentException("Argument_EmptyPath", nameof(path));
119138

120139

140+
var longRunningOperationMessage = !suppressLongRunningOperationMessage ? "Running append operation on " + path : null;
121141

122142
while (true) //roland
123143
{
@@ -160,13 +180,23 @@ await InternalWriteAllTextAsync
160180
)
161181
{
162182
//retry after delay
183+
184+
if (longRunningOperationMessage != null)
185+
{
186+
await ConsoleWatch.AddMessage(ConsoleColor.Yellow, "RETRYING " + longRunningOperationMessage, DateTime.Now, token: cancellationToken, suppressLogFile: suppressLogFile); //NB! suppressLogFile to avoid infinite recursion
187+
}
163188
#if !NOASYNC
164189
await Task.Delay(1000, cancellationToken); //TODO: config file?
165190
#else
166191
cancellationToken.WaitHandle.WaitOne(1000);
167192
#endif
168193
}
194+
} //while (true)
195+
196+
if (longRunningOperationMessage != null)
197+
{
198+
await ConsoleWatch.AddMessage(ConsoleColor.Red, "FAILED AND GIVING UP " + longRunningOperationMessage, DateTime.Now, token: cancellationToken, suppressLogFile: suppressLogFile); //NB! suppressLogFile to avoid infinite recursion
169199
}
170-
}
200+
} //public static async Task AppendAllTextAsync()
171201
}
172202
}

FileInfoCache.cs

+19-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (c) Roland Pihlakas 2019 - 2022
2+
// Copyright (c) Roland Pihlakas 2019 - 2023
33
44
//
55
// Roland Pihlakas licenses this file to you under the GNU Lesser General Public License, ver 2.1.
@@ -43,7 +43,7 @@ public static async Task<Dictionary<string, CachedFileInfo>> ReadFileInfoCache(s
4343

4444
if (await Extensions.FSOperation
4545
(
46-
() => File.Exists(cacheFileName),
46+
cancellationAndTimeoutToken => File.Exists(cacheFileName),
4747
cacheFileName,
4848
Global.CancellationToken.Token,
4949
timeout: 0, //NB!
@@ -101,7 +101,7 @@ public static async Task SaveFileInfoCache(Dictionary<string, CachedFileInfo> di
101101

102102
if (!await Extensions.FSOperation
103103
(
104-
() => Directory.Exists(cacheDirName),
104+
cancellationAndTimeoutToken => Directory.Exists(cacheDirName),
105105
cacheDirName,
106106
Global.CancellationToken.Token,
107107
timeout: 0, //NB!
@@ -110,7 +110,7 @@ public static async Task SaveFileInfoCache(Dictionary<string, CachedFileInfo> di
110110
{
111111
await Extensions.FSOperation
112112
(
113-
() => Directory.CreateDirectory(cacheDirName),
113+
cancellationAndTimeoutToken => Directory.CreateDirectory(cacheDirName),
114114
cacheDirName,
115115
Global.CancellationToken.Token,
116116
timeout: 0, //NB!
@@ -127,6 +127,7 @@ await FileExtensions.WriteAllBytesAsync
127127
serialisedData,
128128
createTempFileFirst: true,
129129
cancellationToken: Global.CancellationToken.Token,
130+
retryCount: 5, //TODO: config
130131
timeout: 0, //NB!
131132
suppressLongRunningOperationMessage: true //NB!
132133
);
@@ -137,6 +138,8 @@ await FileExtensions.WriteAllBytesAsync
137138
public static async Task RefreshFileInfo(WatcherContext context)
138139
{
139140
var fileInfo = context.Event.FileSystemInfo as FileInfo;
141+
//var fileInfo = context.Event.FileSystemInfo.AsFileInfo(); // as FileInfo;
142+
140143
if (fileInfo != null && !context.FileInfoRefreshed.Value)
141144
//var fileInfo = context.FileInfo;
142145
//if (!context.FileInfoRefreshed.Value)
@@ -145,7 +148,7 @@ public static async Task RefreshFileInfo(WatcherContext context)
145148

146149
await Extensions.FSOperation
147150
(
148-
() =>
151+
cancellationAndTimeoutToken =>
149152
{
150153
fileInfo.Refresh(); //https://stackoverflow.com/questions/7828132/getting-current-file-length-fileinfo-length-caching-and-stale-information
151154
if (fileInfo.Exists)
@@ -227,7 +230,7 @@ internal static async Task SaveFileInfo(string fileName, CachedFileInfo fileInfo
227230
if (Global.PersistentCacheDestAndHistoryFolders && removed1)
228231
{
229232
if (dirName == null)
230-
dirName = Path.GetDirectoryName(fileName);
233+
dirName = FolderSyncNetSource.Path.GetDirectoryName(fileName);
231234

232235
dirName = Extensions.GetLongPath(Extensions.GetDirPathWithTrailingSlash(dirName));
233236

@@ -249,7 +252,7 @@ internal static async Task SaveFileInfo(string fileName, CachedFileInfo fileInfo
249252
}
250253
#endif
251254
}
252-
else
255+
else //if (fileInfo == null)
253256
{
254257
var fullName = Extensions.GetLongPath(fileName);
255258
Global.DestAndHistoryFileInfoCache[fullName.ToUpperInvariantOnWindows()] = fileInfo;
@@ -258,7 +261,7 @@ internal static async Task SaveFileInfo(string fileName, CachedFileInfo fileInfo
258261
if (Global.PersistentCacheDestAndHistoryFolders)
259262
{
260263
if (dirName == null)
261-
dirName = Path.GetDirectoryName(fileName);
264+
dirName = FolderSyncNetSource.Path.GetDirectoryName(fileName);
262265

263266
dirName = Extensions.GetLongPath(Extensions.GetDirPathWithTrailingSlash(dirName));
264267

@@ -275,7 +278,7 @@ internal static async Task SaveFileInfo(string fileName, CachedFileInfo fileInfo
275278
}
276279
}
277280
}
278-
}
281+
} //if (fileInfo == null)
279282
} //internal static async Task SaveFileInfo(string fileName, CachedFileInfo fileInfo, CancellationToken cancellationToken = default(CancellationToken))
280283

281284
private static Task<CachedFileInfo> GetFileInfo(WatcherContext context)
@@ -305,7 +308,7 @@ private static async Task<CachedFileInfo> GetFileInfo(string fullName, Cancellat
305308

306309
var fileInfo = await Extensions.FSOperation
307310
(
308-
() =>
311+
cancellationAndTimeoutToken =>
309312
{
310313
var fileInfo1 = new FileInfo(fullName);
311314

@@ -452,7 +455,10 @@ private static async Task<long> GetFileSize(FileInfoRef otherFileInfo, string ot
452455

453456
//NB! no RefreshFileInfo or GetFileExists calls here
454457

455-
return otherFileInfo.Value.Length.Value;
458+
if (!otherFileInfo.Value.Exists.Value)
459+
return -1;
460+
else
461+
return otherFileInfo.Value.Length.Value;
456462
}
457463

458464
public static async Task InvalidateFileDataInPersistentCache(WatcherContext context)
@@ -466,14 +472,14 @@ public static async Task InvalidateFileDataInPersistentCache(WatcherContext cont
466472
var otherFullName = await GetOtherFullName(context);
467473
var longOtherFullName = Extensions.GetLongPath(otherFullName);
468474

469-
var otherDirName = Extensions.GetDirPathWithTrailingSlash(Path.GetDirectoryName(otherFullName));
475+
var otherDirName = Extensions.GetDirPathWithTrailingSlash(FolderSyncNetSource.Path.GetDirectoryName(otherFullName));
470476
var longOtherDirName = Extensions.GetLongPath(otherDirName);
471477

472478
using (await Global.PersistentCacheLocks.LockAsync(longOtherDirName.ToUpperInvariantOnWindows(), context.Token))
473479
{
474480
var cachedFileInfos = await ReadFileInfoCache(longOtherDirName, context.ForHistory);
475481

476-
if (cachedFileInfos != null)
482+
if (cachedFileInfos != null) //TODO: if cachedFileInfos == null then scan entire folder and create cached file infos file
477483
{
478484
cachedFileInfos.Remove(GetNonFullName(longOtherFullName).ToUpperInvariantOnWindows());
479485

0 commit comments

Comments
 (0)