wasStitchNET – Blame information for rev 21

Subversion Repositories:
Rev:
Rev Author Line No. Line
13 office 1 ///////////////////////////////////////////////////////////////////////////
2 // Copyright (C) Wizardry and Steamworks 2017 - License: GNU GPLv3 //
3 // Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
4 // rights of fair usage, the disclaimer and warranty conditions. //
5 ///////////////////////////////////////////////////////////////////////////
6  
7 using System;
8 using System.Collections.Generic;
9 using System.IO;
10 using System.Linq;
11 using System.Text.RegularExpressions;
12 using System.Threading.Tasks;
13 using System.Xml.Linq;
14 using wasDAVClient;
15 using wasSharp;
20 office 16 using wasSharp.Sets;
13 office 17 using wasSharpNET.IO.Utilities;
18 using wasStitchNET.Structures;
19 using XML = wasStitchNET.Patchers.XML;
20  
21 namespace wasStitchNET.Repository.Stitching
22 {
23 public class Stitching
24 {
25 /// <summary>
26 /// Delegate to subscribe to for stitch progress events.
27 /// </summary>
28 /// <param name="sender">the sender</param>
29 /// <param name="e">stitch progress event arguments</param>
30 public delegate void StitchProgressEventHandler(object sender, StitchProgressEventArgs e);
31  
32 /// <summary>
33 /// Stitch progress event handler.
34 /// </summary>
35 public event StitchProgressEventHandler OnProgressUpdate;
36  
37 /// <summary>
38 /// Commodity method to raise stitching progress events.
39 /// </summary>
40 /// <param name="status">the current stitching status</param>
41 private void StitchProgressUpdate(string status)
42 {
43 // Make sure someone is listening to event
44 if (OnProgressUpdate == null) return;
45  
46 var args = new StitchProgressEventArgs(status);
47 OnProgressUpdate(this, args);
48 }
49  
50 /// <summary>
51 /// Stitch!
52 /// </summary>
53 /// <param name="client">the was DAV client to use</param>
54 /// <param name="server">the repository URL to stitch from</param>
55 /// <param name="version">the release to stitch to</param>
56 /// <param name="path">the path to the local files to be stitched</param>
57 /// <param name="nopatch">true if files should not be patched</param>
58 /// <param name="clean">whether to perform a clean stitching by removing local files</param>
59 /// <param name="force">whether to force stitching repository paths</param>
60 /// <param name="noverify">true if remote files should not be checked against official checksum</param>
61 /// <param name="dryrun">whether to perform a dryrun run operation without making any changes</param>
62 /// <returns>true if stitching completed successfully</returns>
63 public async Task<bool> Stitch(Client client, string server, string version, string path,
64 bool nopatch = false,
65 bool clean = false, bool force = false, bool noverify = false, bool dryrun = false)
66 {
67 // Set the server.
68 client.Server = server;
69  
70 // The repository path to the version to update.
71 var updateVersionPath = @"/" +
72 string.Join(@"/", STITCH_CONSTANTS.UPDATE_PATH,
73 STITCH_CONSTANTS.PROGRESSIVE_PATH,
74 version);
75 // Check that the repository has the requested version.
76 StitchProgressUpdate("Attempting to retrieve remote repository update version folder.");
77 try
78 {
79 if (!client.GetFolder(updateVersionPath).Result.IsCollection)
80 throw new Exception();
81 }
82 catch (Exception)
83 {
84 throw new StitchException("The repository does not have requested version available.");
85 }
86  
87 // The repository path to the checksum file of the version to update.
88 var updateChecksumPath = string.Join(@"/", updateVersionPath, STITCH_CONSTANTS.UPDATE_CHECKSUM_FILE);
89  
90 // Attempt to retrieve remote checksum file and normalize the hash.
91 StitchProgressUpdate("Retrieving remote repository checksum file.");
92 string updateChecksum;
93 try
94 {
95 using (var stream = client.Download(updateChecksumPath).Result)
96 {
97 using (var reader = new StreamReader(stream))
98 {
99 // Trim any spaces since we only care about a single-line hash.
100 updateChecksum = Regex.Replace(reader.ReadToEnd(), @"\s+", string.Empty);
101 }
102 }
103 }
104 catch (Exception ex)
105 {
106 throw new StitchException("Unable to retrieve repository checksum file.", ex);
107 }
108  
109 if (string.IsNullOrEmpty(updateChecksum))
110 throw new StitchException("Empty repository update checksum.");
111  
112 // Hash the remote repository files.
113 StitchProgressUpdate("Hashing remote repository checksum files.");
114 string remoteChecksum;
115 try
116 {
117 remoteChecksum = await Hashing.HashRemoteFiles(client,
118 string.Join(@"/", version, STITCH_CONSTANTS.UPDATE_DATA_PATH));
119 }
120 catch (Exception ex)
121 {
122 throw new StitchException("Unable to compute remote checksum.", ex);
123 }
124  
125 if (string.IsNullOrEmpty(remoteChecksum))
126 throw new StitchException("Empty remote checksum.");
127  
128 // Check that the repository checksum file matches the repository file hash.
129 StitchProgressUpdate("Comparing remote repository checksum against remote repository files checksum.");
130 if (!string.Equals(updateChecksum, remoteChecksum, StringComparison.OrdinalIgnoreCase))
131 throw new StitchException("Repository file checksum mismatch.");
132  
133 // Check that the computed repository file checksum matches the official repository checksum file.
134 if (!noverify)
135 {
136 StitchProgressUpdate("Preparing to verify remote repository file checksum to official checksum.");
137  
138 // Retrieve the official repository checksum file for the requested stitch version.
139 StitchProgressUpdate("Retrieving official repository checksum for requested release version.");
140 string officialChecksum;
141 try
142 {
143 // Point the server to the official server.
144 client.Server = STITCH_CONSTANTS.OFFICIAL_UPDATE_SERVER;
145 using (var stream = client.Download(updateChecksumPath).Result)
146 {
147 using (var reader = new StreamReader(stream))
148 {
149 // Trim any spaces since we only care about a single-line hash.
150 officialChecksum = Regex.Replace(reader.ReadToEnd(), @"\s+", string.Empty);
151 }
152 }
153 }
154 catch (Exception ex)
155 {
156 throw new StitchException("Unable to retrieve official repository checksum file.", ex);
157 }
158 finally
159 {
160 client.Server = server;
161 }
162  
163 if (string.IsNullOrEmpty(officialChecksum))
164 throw new StitchException("Unable to retrieve official repository checksum file.");
165  
166 // Compare the official checksum to the repository file checksum.
167 StitchProgressUpdate(
168 $"Comparing official repository checksum ({officialChecksum}) against remote repository files checksum ({remoteChecksum}).");
169 if (!string.Equals(officialChecksum, remoteChecksum, StringComparison.OrdinalIgnoreCase))
170 throw new StitchException("Repository file checksum does not match official repository checksum.");
171 }
172  
173  
174 var stitchOptions = new StitchOptions();
175 var optionsPath = @"/" +
176 string.Join(@"/", STITCH_CONSTANTS.UPDATE_PATH, STITCH_CONSTANTS.PROGRESSIVE_PATH,
177 version, STITCH_CONSTANTS.UPDATE_OPTIONS_FILE);
178  
179 // Retrieve the repository upgrade options file.
180 StitchProgressUpdate("Retrieving remote repository options.");
181 try
182 {
183 using (var stream = client.Download(optionsPath).Result)
184 {
185 stitchOptions = stitchOptions.Load(stream);
186 }
187 }
188 catch (Exception ex)
189 {
190 throw new StitchException("Unable to retrieve repository options.", ex);
191 }
192  
193 // Retrieve the remote repository release files.
194 StitchProgressUpdate("Retrieving remote repository release files.");
195 var remoteFiles = new HashSet<StitchFile>();
196 try
197 {
198 remoteFiles.UnionWith(
199 Files.LoadRemoteFiles(client,
200 string.Join(@"/", version, STITCH_CONSTANTS.UPDATE_DATA_PATH),
201 @"/" + string.Join(@"/", STITCH_CONSTANTS.UPDATE_PATH, STITCH_CONSTANTS.PROGRESSIVE_PATH,
202 version, STITCH_CONSTANTS.UPDATE_DATA_PATH)
203 ));
204 }
205 catch (Exception ex)
206 {
207 throw new StitchException("Unable to download repository release files.", ex);
208 }
209  
210 // Retrieve local path files.
211 StitchProgressUpdate("Retrieving local path files.");
212 var localFiles = new HashSet<StitchFile>();
213 try
214 {
215 localFiles.UnionWith(Files.LoadLocalFiles(path,
216 path,
217 Path.DirectorySeparatorChar));
218 }
219 catch (Exception ex)
220 {
221 throw new StitchException("Unable to load local files.", ex);
222 }
223  
224 // Files to be wiped.
225 var wipeFiles = new HashSet<StitchFile>();
226 if (clean)
227 switch (stitchOptions.Force || force)
228 {
229 case true:
230 wipeFiles.UnionWith(localFiles.Except(remoteFiles));
231 break;
232  
233 default:
234 wipeFiles.UnionWith(
235 localFiles.Except(remoteFiles)
236 .Where(
237 o =>
238 stitchOptions.FileExcludes.Path.All(
239 p =>
240 o.Path.SequenceExcept(p.PathSplit(Path.DirectorySeparatorChar))
241 .Count()
242 .Equals(o.Path.Count()))));
243 break;
244 }
245  
246 // Files to be stitched.
247 var stitchFiles = new HashSet<StitchFile>();
248 // If the force option was specified then stitch all the files that are not in the remote
249 // repository by ignoring any excludes.
250 switch (stitchOptions.Force || force)
251 {
252 case true:
253 stitchFiles.UnionWith(remoteFiles.Except(localFiles));
254 break;
255  
256 default:
257 stitchFiles.UnionWith(
258 remoteFiles.Except(localFiles)
259 .Where(
260 o =>
261 stitchOptions.FileExcludes.Path.All(
262 p =>
263 o.Path.SequenceExcept(p.PathSplit(Path.DirectorySeparatorChar))
264 .Count()
265 .Equals(o.Path.Count()))));
266 break;
267 }
268  
269 // Wipe local files and directories that have to be removed.
270 StitchProgressUpdate("Removing local files and folders.");
271 var directories = new Queue<string>();
272 foreach (var file in wipeFiles)
273 {
274 var deletePath = string.Join(Path.DirectorySeparatorChar.ToString(),
275 path,
276 string.Join(Path.DirectorySeparatorChar.ToString(), file.Path));
277 try
278 {
279 switch (file.PathType)
280 {
281 case StitchPathType.PATH_FILE:
282 if (!dryrun)
283 File.Delete(deletePath);
284 break;
285  
286 case StitchPathType.PATH_DIRECTORY: // we cannot delete the directories right away.
287 directories.Enqueue(deletePath);
288 break;
289 }
290 }
291 catch (Exception ex)
292 {
293 throw new StitchException("Unable remove local files.", ex);
294 }
295 }
296  
297 directories = new Queue<string>(directories.OrderByDescending(o => o));
298  
299 while (directories.Any())
300 {
301 var deletePath = directories.Dequeue();
302 try
303 {
304 if (!dryrun)
305 Directory.Delete(deletePath);
306 }
307 catch (Exception ex)
308 {
309 throw new StitchException("Unable remove local directories.", ex);
310 }
311 }
312  
313 // Stitch files that have to be stitched.
314 StitchProgressUpdate("Stitching files.");
315 foreach (var file in stitchFiles)
316 try
317 {
318 var stitchRemotePath = @"/" + string.Join(@"/", STITCH_CONSTANTS.UPDATE_PATH,
319 STITCH_CONSTANTS.PROGRESSIVE_PATH,
320 version, STITCH_CONSTANTS.UPDATE_DATA_PATH,
321 string.Join("/", file.Path));
322 var stitchLocalPath = string.Join(Path.DirectorySeparatorChar.ToString(),
323 path.PathSplit(Path.DirectorySeparatorChar)
324 .Concat(file.Path));
325  
326 switch (file.PathType)
327 {
328 case StitchPathType.PATH_DIRECTORY:
329 // Create the directory.
330 if (!dryrun)
331 Directory.CreateDirectory(stitchLocalPath);
332 continue;
333 case StitchPathType.PATH_FILE:
334 // Create the directory to the stitch file.
335 if (!dryrun)
336 Directory.CreateDirectory(
337 string.Join(Path.DirectorySeparatorChar.ToString(),
338 stitchLocalPath.PathSplit(Path.DirectorySeparatorChar).Reverse()
339 .Skip(1)
340 .Reverse()));
341 break;
342 }
343  
344 using (var memoryStream = new MemoryStream())
345 {
346 using (var stream = client.Download(stitchRemotePath).Result)
347 {
348 stream.CopyTo(memoryStream);
349 }
350 memoryStream.Position = 0L;
351 if (!dryrun)
352 using (var fileStream =
353 IOExtensions.GetWriteStream(stitchLocalPath, FileMode.Create,
354 FileAccess.Write, FileShare.None, STITCH_CONSTANTS.LOCAL_FILE_ACCESS_TIMEOUT))
355 {
356 memoryStream.CopyTo(fileStream);
357 }
358 }
359 }
360 catch (Exception ex)
361 {
362 throw new StitchException("Unable to stitch files.", ex);
363 }
364  
365 // If no file patches was requested then do not patch and the process is complete.
366 if (nopatch)
367 return true;
368  
369 StitchProgressUpdate("Patching files.");
370  
371 // Retrive working file.
372 var workingFilePath = string.Join(Path.DirectorySeparatorChar.ToString(),
21 office 373 path, STITCH_CONSTANTS.WORKING_CONFIGURATION_FILE);
13 office 374  
375 StitchProgressUpdate("Parsing working file to be patched.");
376 XDocument workingFile;
377 try
378 {
379 workingFile = XDocument.Load(workingFilePath);
380 }
381 catch (Exception ex)
382 {
383 throw new StitchException("Unable to parse working file to be patched.", ex);
384 }
385  
386 // Retrieve default file.
387 StitchProgressUpdate("Parsing default file to be patched.");
388 XDocument defaultFile;
389 try
390 {
391 defaultFile = XDocument.Load(string.Join(Path.DirectorySeparatorChar.ToString(),
21 office 392 path, STITCH_CONSTANTS.DEFAULT_CONFIGURATION_FILE));
13 office 393 }
394 catch (Exception ex)
395 {
396 throw new StitchException("Unable to parse default file to be patched.", ex);
397 }
398  
399 // XPaths to exclude from patching.
400 var excludeXPaths = new HashSet<string>();
401 if (stitchOptions.ConfigurationExcludes != null)
402 excludeXPaths.UnionWith(stitchOptions.ConfigurationExcludes.Tag);
403  
404 // XPaths to force whilst patching.
405 var forceXPaths = new HashSet<string>();
406 if (stitchOptions.ConfigurationForce != null)
407 forceXPaths.UnionWith(stitchOptions.ConfigurationForce.Tag);
408  
409 // Patch the file.
410 StitchProgressUpdate("Patching file.");
411 var patchedFile = XML
412 .PatchXDocument(workingFile, defaultFile, forceXPaths, excludeXPaths);
413 if (patchedFile == null)
414 throw new StitchException("Unable to patch XML files.");
415  
416 // Create a backup for the file to be patched.
417 StitchProgressUpdate("Creating a backup of the file to be patched.");
418 try
419 {
420 if (!dryrun)
421 File.Copy(workingFilePath,
422 string.Join(Path.DirectorySeparatorChar.ToString(),
21 office 423 path, STITCH_CONSTANTS.BACKUP_CONFIGURATION_FILE), true);
13 office 424 }
425 catch (Exception ex)
426 {
427 throw new StitchException("Unable to create patched file backup.", ex);
428 }
429  
430 // Write the patched file.
431 StitchProgressUpdate("Saving the patched file.");
432 try
433 {
434 if (!dryrun)
435 patchedFile.Save(string.Join(Path.DirectorySeparatorChar.ToString(),
21 office 436 path, STITCH_CONSTANTS.WORKING_CONFIGURATION_FILE));
13 office 437 }
438 catch (Exception ex)
439 {
440 throw new StitchException("Unable to save patched file.", ex);
441 }
442  
443 StitchProgressUpdate("Stitching successful.");
444 return true;
445 }
446 }
447 }