10
10
using System ;
11
11
using System . Data . Linq ;
12
12
using System . IO ;
13
+ using System . Runtime . InteropServices ;
13
14
using System . Threading ;
14
15
using System . Threading . Tasks ;
16
+ using Alphaleonis . Win32 . Vss ;
15
17
16
18
namespace FolderSync
17
19
{
@@ -25,7 +27,7 @@ public static bool BinaryEqual(Binary a, Binary b)
25
27
return a . Equals ( b ) ;
26
28
}
27
29
28
- public static async Task < Tuple < byte [ ] , long > > ReadAllBytesAsync ( string path , CancellationToken cancellationToken = default ( CancellationToken ) , long maxFileSize = 0 , int retryCount = 0 , int readBufferKB = 0 , int bufferReadDelayMs = 0 , int ? timeout = null , bool suppressLongRunningOperationMessage = false )
30
+ public static async Task < Tuple < byte [ ] , long > > ReadAllBytesAsync ( string path , bool allowVSS , CancellationToken cancellationToken = default ( CancellationToken ) , long maxFileSize = 0 , int retryCount = 0 , int readBufferKB = 0 , int bufferReadDelayMs = 0 , int ? timeout = null , bool suppressLongRunningOperationMessage = false )
29
31
{
30
32
if ( path == null )
31
33
throw new ArgumentNullException ( nameof ( path ) ) ;
@@ -41,72 +43,32 @@ public static bool BinaryEqual(Binary a, Binary b)
41
43
42
44
try
43
45
{
44
- using ( var stream = new FileStream
45
- (
46
- path ,
47
- FileMode . Open ,
48
- FileAccess . Read ,
49
- FileShare . ReadWrite ,
50
- bufferSize : 1024 * 1024 ,
51
- useAsync : true
52
- ) )
46
+ if ( allowVSS )
53
47
{
54
- long longLen = stream . Length ; //NB! the length might change during the code execution, so need to save it into separate variable
55
-
56
- maxFileSize = Math . Min ( MaxByteArraySize , maxFileSize ) ;
57
- if ( maxFileSize > 0 && longLen > maxFileSize )
58
- {
59
- return new Tuple < byte [ ] , long > ( null , longLen ) ;
48
+ try
49
+ {
50
+ var result = await ReadAllBytesWithVSSAsync ( path , cancellationToken , maxFileSize , readBufferKB , bufferReadDelayMs , timeout , suppressLongRunningOperationMessage ) ;
51
+ return result ;
60
52
}
61
-
62
-
63
- int len = ( int ) longLen ;
64
- byte [ ] result = new byte [ len ] ;
65
-
66
- #if false
67
- //await stream.ReadAsync(result, 0, (int)len, cancellationToken);
68
- await Extensions . FSOperation
69
- (
70
- async ( ) => await stream . ReadAsync ( result , 0 , ( int ) len , cancellationToken ) ,
71
- path ,
72
- cancellationToken ,
73
- timeout : timeout ?? Global . FileBufferReadTimeout ,
74
- suppressLongRunningOperationMessage : suppressLongRunningOperationMessage
75
- ) ;
76
- #else
77
- var readBufferLength = readBufferKB * 1024 ;
78
- if ( readBufferLength <= 0 /* || bufferReadDelayMs <= 0*/ ) //NB! disable write buffer length limit if delay is 0
79
- readBufferLength = len ;
80
-
81
- for ( int readOffset = 0 ; readOffset < len ; readOffset += readBufferLength )
53
+ catch ( VssException )
82
54
{
83
- if ( readOffset > 0 && bufferReadDelayMs > 0 )
84
- {
85
- #if ! NOASYNC
86
- await Task . Delay ( bufferReadDelayMs , cancellationToken ) ;
87
- #else
88
- cancellationToken . WaitHandle . WaitOne ( bufferReadDelayMs ) ;
89
- #endif
90
- }
91
-
92
- //await stream.WriteAsync(contents, i, writeBufferLength, cancellationToken);
93
- await Extensions . FSOperation
94
- (
95
- async ( ) => await stream . ReadAsync ( result , readOffset , Math . Min ( readBufferLength , len - readOffset ) , cancellationToken ) ,
96
- path ,
97
- cancellationToken ,
98
- timeout : timeout ?? Global . FileBufferReadTimeout ,
99
- suppressLongRunningOperationMessage : suppressLongRunningOperationMessage
100
- ) ;
101
- } //for (int i = 0; i < contents.Length; i += writeBufferLength)
102
- #endif
103
-
104
- return new Tuple < byte [ ] , long > ( result , len ) ;
55
+ var result = await ReadAllBytesNoVSSAsync ( path , cancellationToken , maxFileSize , readBufferKB , bufferReadDelayMs , timeout , suppressLongRunningOperationMessage ) ;
56
+ return result ;
57
+ }
58
+ }
59
+ else
60
+ {
61
+ var result = await ReadAllBytesNoVSSAsync ( path , cancellationToken , maxFileSize , readBufferKB , bufferReadDelayMs , timeout , suppressLongRunningOperationMessage ) ;
62
+ return result ;
105
63
}
106
64
}
107
65
catch ( Exception ex ) when (
108
- ex is IOException
66
+ /*ex is IOException
67
+ || ex is TimeoutException
109
68
|| ex is UnauthorizedAccessException //can happen when a folder was just created //TODO: abandon retries after a certain number of attempts in this case
69
+ || */ ex . GetInnermostException ( ) is IOException
70
+ || ex . GetInnermostException ( ) is TimeoutException
71
+ || ex . GetInnermostException ( ) is UnauthorizedAccessException
110
72
)
111
73
{
112
74
//retry after delay
@@ -126,7 +88,109 @@ ex is IOException
126
88
127
89
} //public static async Task<Tuple<byte[], long>> ReadAllBytesAsync()
128
90
129
- public static async Task WriteAllBytesAsync ( string path , byte [ ] contents , bool createTempFileFirst , CancellationToken cancellationToken = default ( CancellationToken ) , int writeBufferKB = 0 , int bufferWriteDelayMs = 0 , int ? timeout = null , bool suppressLongRunningOperationMessage = false )
91
+ public static async Task < Tuple < byte [ ] , long > > ReadAllBytesWithVSSAsync ( string path , CancellationToken cancellationToken , long maxFileSize , int readBufferKB , int bufferReadDelayMs , int ? timeout , bool suppressLongRunningOperationMessage )
92
+ {
93
+ if ( path . StartsWith ( @"\\?\" ) ) //roland: VSS does not like \\?\ paths
94
+ path = path . Substring ( 4 ) ;
95
+
96
+ using ( var vss = new VssBackup ( ) )
97
+ {
98
+ vss . Setup ( Path . GetPathRoot ( path ) ) ;
99
+
100
+ #if true
101
+ using ( var stream = vss . GetStream ( path ) )
102
+ #else
103
+ using ( var stream = new FileStream
104
+ (
105
+ vss . GetSnapshotPath ( path ) ,
106
+ FileMode . Open ,
107
+ FileAccess . Read ,
108
+ FileShare . Read , //Write,
109
+ bufferSize : 1024 * 1024 ,
110
+ //useAsync: true
111
+ options : FileOptions . Asynchronous | FileOptions . SequentialScan
112
+ ) )
113
+ #endif
114
+ {
115
+ var result = await ReadAllBytesFromStreamAsync ( stream , path , cancellationToken , maxFileSize , readBufferKB , bufferReadDelayMs , timeout , suppressLongRunningOperationMessage ) ;
116
+ return result ;
117
+ }
118
+ }
119
+ }
120
+
121
+ public static async Task < Tuple < byte [ ] , long > > ReadAllBytesNoVSSAsync ( string path , CancellationToken cancellationToken , long maxFileSize , int readBufferKB , int bufferReadDelayMs , int ? timeout , bool suppressLongRunningOperationMessage )
122
+ {
123
+ using ( var stream = new FileStream
124
+ (
125
+ path ,
126
+ FileMode . Open ,
127
+ FileAccess . Read ,
128
+ FileShare . ReadWrite ,
129
+ bufferSize : 1024 * 1024 ,
130
+ //useAsync: true
131
+ options : FileOptions . Asynchronous | FileOptions . SequentialScan
132
+ ) )
133
+ {
134
+ var result = await ReadAllBytesFromStreamAsync ( stream , path , cancellationToken , maxFileSize , readBufferKB , bufferReadDelayMs , timeout , suppressLongRunningOperationMessage ) ;
135
+ return result ;
136
+ }
137
+ }
138
+
139
+ public static async Task < Tuple < byte [ ] , long > > ReadAllBytesFromStreamAsync ( Stream stream , string path , CancellationToken cancellationToken , long maxFileSize , int readBufferKB , int bufferReadDelayMs , int ? timeout , bool suppressLongRunningOperationMessage )
140
+ {
141
+ long longLen = stream . Length ; //NB! the length might change during the code execution, so need to save it into separate variable
142
+
143
+ maxFileSize = Math . Min ( MaxByteArraySize , maxFileSize ) ;
144
+ if ( maxFileSize > 0 && longLen > maxFileSize )
145
+ {
146
+ return new Tuple < byte [ ] , long > ( null , longLen ) ;
147
+ }
148
+
149
+
150
+ int len = ( int ) longLen ;
151
+ byte [ ] result = new byte [ len ] ;
152
+
153
+ #if false
154
+ //await stream.ReadAsync(result, 0, (int)len, cancellationToken);
155
+ await Extensions . FSOperation
156
+ (
157
+ async ( ) => await stream . ReadAsync ( result , 0 , ( int ) len , cancellationToken ) ,
158
+ path ,
159
+ cancellationToken ,
160
+ timeout : timeout ?? Global . FileBufferReadTimeout ,
161
+ suppressLongRunningOperationMessage : suppressLongRunningOperationMessage
162
+ ) ;
163
+ #else
164
+ var readBufferLength = readBufferKB * 1024 ;
165
+ if ( readBufferLength <= 0 /* || bufferReadDelayMs <= 0*/ ) //NB! disable read buffer length limit if delay is 0
166
+ readBufferLength = len ;
167
+
168
+ for ( int readOffset = 0 ; readOffset < len ; readOffset += readBufferLength )
169
+ {
170
+ if ( readOffset > 0 && bufferReadDelayMs > 0 )
171
+ {
172
+ #if ! NOASYNC
173
+ await Task . Delay ( bufferReadDelayMs , cancellationToken ) ;
174
+ #else
175
+ cancellationToken . WaitHandle . WaitOne ( bufferReadDelayMs ) ;
176
+ #endif
177
+ }
178
+
179
+ await Extensions . FSOperation
180
+ (
181
+ async ( ) => await stream . ReadAsync ( result , readOffset , Math . Min ( readBufferLength , len - readOffset ) , cancellationToken ) ,
182
+ path ,
183
+ cancellationToken ,
184
+ timeout : timeout ?? Global . FileBufferReadTimeout ,
185
+ suppressLongRunningOperationMessage : suppressLongRunningOperationMessage
186
+ ) ;
187
+ } //for (int i = 0; i < contents.Length; i += writeBufferLength)
188
+ #endif
189
+
190
+ return new Tuple < byte [ ] , long > ( result , len ) ;
191
+ }
192
+
193
+ public static async Task WriteAllBytesAsync ( string path , byte [ ] contents , bool createTempFileFirst , CancellationToken cancellationToken = default ( CancellationToken ) , int retryCount = 0 , int writeBufferKB = 0 , int bufferWriteDelayMs = 0 , int ? timeout = null , bool suppressLongRunningOperationMessage = false )
130
194
{
131
195
if ( path == null )
132
196
throw new ArgumentNullException ( nameof ( path ) ) ;
@@ -137,7 +201,8 @@ ex is IOException
137
201
if ( createTempFileFirst )
138
202
tempPath += ".tmp" ;
139
203
140
- while ( true )
204
+ retryCount = Math . Max ( 0 , retryCount ) ;
205
+ for ( int tryIndex = - 1 ; tryIndex < retryCount ; tryIndex ++ )
141
206
{
142
207
cancellationToken . ThrowIfCancellationRequested ( ) ;
143
208
@@ -149,8 +214,9 @@ ex is IOException
149
214
FileMode . OpenOrCreate ,
150
215
FileAccess . Write ,
151
216
FileShare . Read ,
152
- bufferSize : 1024 * 1024 ,
153
- useAsync : true
217
+ bufferSize : 1024 * 1024 ,
218
+ //useAsync: true
219
+ options : FileOptions . Asynchronous | FileOptions . SequentialScan
154
220
) )
155
221
{
156
222
var writeBufferLength = writeBufferKB * 1024 ;
@@ -228,7 +294,14 @@ await Extensions.FSOperation
228
294
229
295
return ; //exit while loop
230
296
}
231
- catch ( IOException )
297
+ catch ( Exception ex ) when (
298
+ /*ex is IOException
299
+ || ex is TimeoutException
300
+ || ex is UnauthorizedAccessException //can happen when a folder was just created //TODO: abandon retries after a certain number of attempts in this case
301
+ || */ ex . GetInnermostException ( ) is IOException
302
+ || ex . GetInnermostException ( ) is TimeoutException
303
+ || ex . GetInnermostException ( ) is UnauthorizedAccessException
304
+ )
232
305
{
233
306
//retry after delay
234
307
@@ -238,7 +311,10 @@ await Extensions.FSOperation
238
311
cancellationToken . WaitHandle . WaitOne ( 1000 ) ;
239
312
#endif
240
313
}
241
- }
314
+ } //for (int tryIndex = -1; tryIndex < retryCount; tryIndex++)
315
+
316
+ throw new ExternalException ( ) ;
317
+
242
318
} //public static async Task WriteAllBytesAsync()
243
319
244
320
}
0 commit comments