Skip to content

Commit caf8795

Browse files
committed
Added option to use persistent dirlist cache for mirror and history destination paths. Useful in case the destination is on slow network drives.
1 parent efeac28 commit caf8795

File tree

5 files changed

+709
-224
lines changed

5 files changed

+709
-224
lines changed

BinaryFileExtensions.cs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (c) Roland Pihlakas 2019 - 2020
2+
// Copyright (c) Roland Pihlakas 2019 - 2022
33
44
//
55
// Roland Pihlakas licenses this file to you under the GNU Lesser General Public License, ver 2.1.
@@ -27,6 +27,11 @@ public static bool BinaryEqual(Binary a, Binary b)
2727

2828
public static async Task<Tuple<byte[], long>> ReadAllBytesAsync(string path, CancellationToken cancellationToken = default(CancellationToken), long maxFileSize = 0)
2929
{
30+
if (path == null)
31+
throw new ArgumentNullException(nameof(path));
32+
if (path.Length == 0)
33+
throw new ArgumentException("Argument_EmptyPath: {0}", nameof(path));
34+
3035
while (true)
3136
{
3237
if (cancellationToken.IsCancellationRequested)
@@ -76,16 +81,25 @@ public static bool BinaryEqual(Binary a, Binary b)
7681
}
7782
}
7883

79-
public static async Task WriteAllBytesAsync(string path, byte[] contents, CancellationToken cancellationToken = default(CancellationToken), int writeBufferKB = 0, int bufferWriteDelayMs = 0)
84+
public static async Task WriteAllBytesAsync(string path, byte[] contents, bool createTempFileFirst, CancellationToken cancellationToken = default(CancellationToken), int writeBufferKB = 0, int bufferWriteDelayMs = 0)
8085
{
86+
if (path == null)
87+
throw new ArgumentNullException(nameof(path));
88+
if (path.Length == 0)
89+
throw new ArgumentException("Argument_EmptyPath: {0}", nameof(path));
90+
91+
var tempPath = path;
92+
if (createTempFileFirst)
93+
tempPath += ".tmp";
94+
8195
while (true)
8296
{
8397
cancellationToken.ThrowIfCancellationRequested();
8498

8599
try
86100
{
87101
using (var stream = new FileStream(
88-
path,
102+
tempPath,
89103
FileMode.OpenOrCreate,
90104
FileAccess.Write,
91105
FileShare.Read,
@@ -110,9 +124,22 @@ public static bool BinaryEqual(Binary a, Binary b)
110124

111125
await stream.WriteAsync(contents, i, writeBufferLength, cancellationToken);
112126
}
127+
}
113128

114-
return;
129+
if (createTempFileFirst)
130+
{
131+
if (await Extensions.FSOperation(() => File.Exists(path), cancellationToken))
132+
{
133+
#pragma warning disable SEC0116 //Warning SEC0116 Unvalidated file paths are passed to a file delete API, which can allow unauthorized file system operations (e.g. read, write, delete) to be performed on unintended server files.
134+
await Extensions.FSOperation(() => File.Delete(path), cancellationToken);
135+
#pragma warning restore SEC0116
136+
}
137+
138+
await Extensions.FSOperation(() => File.Move(tempPath, path), cancellationToken);
115139
}
140+
141+
142+
return; //exit while loop
116143
}
117144
catch (IOException)
118145
{

Extensions.cs

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (c) Roland Pihlakas 2019 - 2020
2+
// Copyright (c) Roland Pihlakas 2019 - 2022
33
44
//
55
// Roland Pihlakas licenses this file to you under the GNU Lesser General Public License, ver 2.1.
@@ -10,6 +10,9 @@
1010
using System;
1111
using System.Collections.Generic;
1212
using System.IO;
13+
using System.IO.Compression;
14+
using System.Runtime.Serialization;
15+
using System.Runtime.Serialization.Formatters.Binary;
1316
using System.Threading;
1417
using System.Threading.Tasks;
1518
using Nito.AsyncEx;
@@ -43,6 +46,10 @@ public static class Extensions
4346

4447
public static string GetLongPath(string path)
4548
{
49+
if (!ConfigParser.IsWindows)
50+
return Path.GetFullPath(path); //GetFullPath: convert relative path to full path
51+
52+
4653
//@"\\?\" prefix is needed for reading from long paths: https://stackoverflow.com/questions/44888844/directorynotfoundexception-when-using-long-paths-in-net-4-7 and https://superuser.com/questions/1617012/support-of-the-unc-server-share-syntax-in-windows
4754

4855
if (path.Substring(0, 2) == @"\\") //network path or path already starting with \\?\
@@ -51,10 +58,20 @@ public static string GetLongPath(string path)
5158
}
5259
else
5360
{
54-
return @"\\?\" + path;
61+
return @"\\?\" + Path.GetFullPath(path); //GetFullPath: convert relative path to full path
5562
}
5663
}
5764

65+
public static string GetDirPathWithTrailingSlash(string dirPath)
66+
{
67+
if (string.IsNullOrWhiteSpace(dirPath))
68+
return dirPath;
69+
70+
dirPath = Path.Combine(dirPath, "."); //NB! add "." in order to ensure that slash is appended to the end of the path
71+
dirPath = dirPath.Substring(0, dirPath.Length - 1); //drop the "." again
72+
return dirPath;
73+
}
74+
5875
public static async Task FSOperation(Action func, CancellationToken token)
5976
{
6077
//await Task.Run(func).WaitAsync(token);
@@ -96,5 +113,68 @@ public static async Task<T[]> DirListOperation<T>(Func<T[]> func, int retryCount
96113

97114
return new T[0];
98115
}
116+
117+
public static byte[] SerializeBinary<T>(this T obj, bool compress = true)
118+
{
119+
var formatter = new BinaryFormatter();
120+
formatter.Context = new StreamingContext(StreamingContextStates.Persistence);
121+
122+
using (var mstream = new MemoryStream())
123+
{
124+
if (compress)
125+
{
126+
using (var gzStream = new GZipStream(mstream, CompressionLevel.Optimal, leaveOpen: true))
127+
{
128+
formatter.Serialize(gzStream, obj);
129+
}
130+
}
131+
else
132+
{
133+
formatter.Serialize(mstream, obj);
134+
}
135+
136+
byte[] bytes = new byte[mstream.Length];
137+
mstream.Position = 0; //NB! reset stream position
138+
mstream.Read(bytes, 0, (int)mstream.Length);
139+
return bytes;
140+
}
141+
}
142+
143+
public static T DeserializeBinary<T>(this byte[] bytes, bool? decompress = null)
144+
{
145+
var formatter = new BinaryFormatter();
146+
formatter.Context = new StreamingContext(StreamingContextStates.Persistence);
147+
148+
using (var mstream = new MemoryStream(bytes))
149+
{
150+
mstream.Position = 0;
151+
152+
if (decompress == null) //auto detect
153+
{
154+
if (mstream.ReadByte() == 0x1F && mstream.ReadByte() == 0x8B)
155+
decompress = true;
156+
157+
mstream.Position = 0; //reset stream position
158+
}
159+
160+
if (decompress == true)
161+
{
162+
using (var gzStream = new GZipStream(mstream, CompressionMode.Decompress, leaveOpen: true))
163+
{
164+
object result = formatter.Deserialize(gzStream);
165+
if (result is T)
166+
return (T)result;
167+
return default(T);
168+
}
169+
}
170+
else
171+
{
172+
object result = formatter.Deserialize(mstream);
173+
if (result is T)
174+
return (T)result;
175+
return default(T);
176+
}
177+
}
178+
}
99179
}
100180
}

0 commit comments

Comments
 (0)