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