Skip to content

Commit 87e18a3

Browse files
committed
重构清单(Manifest)和发布(Release)信息的序列化和反序列化。 💒
将 JSON 格式改为自定义的 XML 格式。
1 parent c63c533 commit 87e18a3

File tree

10 files changed

+593
-33
lines changed

10 files changed

+593
-33
lines changed

upgrading/.shared/Manifest.cs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,26 @@ public Manifest(Release trunk, Release[] deltas)
8080
#endregion
8181

8282
#region 保存方法
83-
public async ValueTask<string> SaveAsync(string directory, CancellationToken cancellation)
83+
public ValueTask<string> SaveAsync(string directory, CancellationToken cancellation = default)
8484
{
8585
ArgumentException.ThrowIfNullOrEmpty(directory);
86+
87+
if(this.IsEmpty)
88+
return ValueTask.FromResult<string>(null);
89+
8690
using var stream = File.Create(Path.Combine(directory, FileName));
87-
await Serialization.Serializer.Json.SerializeAsync(stream, this, cancellation);
88-
return stream.Name;
91+
92+
if(this.Trunk == null)
93+
Release.Save(stream, this.Deltas);
94+
else
95+
{
96+
var releases = new Release[this.Deltas.Length + 1];
97+
releases[0] = this.Trunk;
98+
this.Deltas.CopyTo(releases, 1);
99+
Release.Save(stream, releases);
100+
}
101+
102+
return ValueTask.FromResult(stream.Name);
89103
}
90104
#endregion
91105

@@ -94,11 +108,14 @@ public static Manifest Load(string path)
94108
{
95109
ArgumentException.ThrowIfNullOrEmpty(path);
96110

97-
if(!File.Exists(path))
111+
var releases = Release.Load(path).ToArray();
112+
if(releases.Length == 0)
98113
return null;
99114

100-
var stream = File.Open(path, FileMode.Open, FileAccess.Read);
101-
return Serialization.Serializer.Json.Deserialize<Manifest>(stream);
115+
if(releases[0].Kind == ReleaseKind.Fully)
116+
return new(releases[0], releases[1..]);
117+
else
118+
return new(releases);
102119
}
103120
#endregion
104121
}
Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
/*
2+
* _____ ______
3+
* /_ / ____ ____ ____ _________ / __/ /_
4+
* / / / __ \/ __ \/ __ \/ ___/ __ \/ /_/ __/
5+
* / /__/ /_/ / / / / /_/ /\_ \/ /_/ / __/ /_
6+
* /____/\____/_/ /_/\__ /____/\____/_/ \__/
7+
* /____/
8+
*
9+
* Authors:
10+
* 钟峰(Popeye Zhong) <zongsoft@qq.com>
11+
*
12+
* Copyright (C) 2020-2026 Zongsoft Studio <http://www.zongsoft.com>
13+
*
14+
* This file is part of Zongsoft.Upgrading library.
15+
*
16+
* The Zongsoft.Upgrading is free software: you can redistribute it and/or modify
17+
* it under the terms of the GNU Lesser General Public License as published by
18+
* the Free Software Foundation, either version 3.0 of the License,
19+
* or (at your option) any later version.
20+
*
21+
* The Zongsoft.Upgrading is distributed in the hope that it will be useful,
22+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
23+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24+
* GNU Lesser General Public License for more details.
25+
*
26+
* You should have received a copy of the GNU Lesser General Public License
27+
* along with the Zongsoft.Upgrading library. If not, see <http://www.gnu.org/licenses/>.
28+
*/
29+
30+
using System;
31+
using System.IO;
32+
using System.Xml;
33+
using System.Threading;
34+
using System.Threading.Tasks;
35+
using System.Collections.Generic;
36+
37+
using Zongsoft.Common;
38+
39+
namespace Zongsoft.Upgrading;
40+
41+
partial class Release
42+
{
43+
#region 公共方法
44+
public static IEnumerable<Release> Load(string filePath)
45+
{
46+
ArgumentException.ThrowIfNullOrEmpty(filePath);
47+
return File.Exists(filePath) ? Load(File.OpenRead(filePath)) : [];
48+
}
49+
public static IEnumerable<Release> Load(Stream stream)
50+
{
51+
ArgumentNullException.ThrowIfNull(stream);
52+
using var reader = XmlReader.Create(stream, GetSettings(false));
53+
54+
if(reader.MoveToContent() == XmlNodeType.Element)
55+
{
56+
if(reader.LocalName == RELEASES_ELEMENT)
57+
{
58+
var depth = reader.Depth;
59+
60+
while(reader.Read() && reader.Depth > depth)
61+
{
62+
var release = Read(reader);
63+
64+
if(release != null)
65+
yield return release;
66+
}
67+
}
68+
else if(reader.LocalName == nameof(RELEASE_ELEMENT))
69+
yield return Read(reader);
70+
}
71+
}
72+
73+
public static IAsyncEnumerable<Release> LoadAsync(string filePath, CancellationToken cancellation = default)
74+
{
75+
ArgumentException.ThrowIfNullOrEmpty(filePath);
76+
return File.Exists(filePath) ? LoadAsync(File.OpenRead(filePath), cancellation) : default;
77+
}
78+
public static async IAsyncEnumerable<Release> LoadAsync(Stream stream, [System.Runtime.CompilerServices.EnumeratorCancellation]CancellationToken cancellation = default)
79+
{
80+
ArgumentNullException.ThrowIfNull(stream);
81+
using var reader = XmlReader.Create(stream, GetSettings(true));
82+
83+
if(reader.MoveToContent() == XmlNodeType.Element)
84+
{
85+
if(reader.LocalName == RELEASES_ELEMENT)
86+
{
87+
var depth = reader.Depth;
88+
89+
while(reader.Read() && reader.Depth > depth)
90+
{
91+
var release = Read(reader);
92+
93+
if(release != null)
94+
yield return release;
95+
}
96+
}
97+
else if(reader.LocalName == nameof(RELEASE_ELEMENT))
98+
yield return Read(reader);
99+
}
100+
}
101+
#endregion
102+
103+
#region 内部方法
104+
internal static Release Read(Stream stream)
105+
{
106+
ArgumentNullException.ThrowIfNull(stream);
107+
using var reader = XmlReader.Create(stream, GetSettings(false));
108+
return Read(reader);
109+
}
110+
internal static Release Read(XmlReader reader)
111+
{
112+
ArgumentNullException.ThrowIfNull(reader);
113+
114+
if(reader.NodeType != XmlNodeType.Element)
115+
return null;
116+
if(reader.LocalName != RELEASE_ELEMENT)
117+
return null;
118+
119+
var depth = reader.Depth;
120+
var release = new Release();
121+
PopulateAttributes(release, reader);
122+
123+
while(reader.Read() && reader.Depth > depth)
124+
{
125+
if(reader.NodeType == XmlNodeType.Element)
126+
{
127+
switch(reader.LocalName)
128+
{
129+
case nameof(Release.Title):
130+
if(reader.Read() && reader.NodeType == XmlNodeType.Text)
131+
release.Title = reader.Value;
132+
133+
break;
134+
case nameof(Release.Summary):
135+
if(reader.Read() && reader.NodeType == XmlNodeType.Text)
136+
release.Summary = reader.Value;
137+
138+
break;
139+
case nameof(Release.Description):
140+
if(reader.Read() && reader.NodeType == XmlNodeType.Text)
141+
release.Description = reader.Value;
142+
143+
break;
144+
case nameof(TAGS_ELEMENT):
145+
PopulateTags(release, reader);
146+
break;
147+
case nameof(EXECUTORS_ELEMENT):
148+
PopulateExecutors(release, reader);
149+
break;
150+
case nameof(PROPERTIES_ELEMENT):
151+
PopulateProperties(release, reader);
152+
break;
153+
}
154+
}
155+
}
156+
157+
return release;
158+
}
159+
160+
internal static ValueTask<Release> ReadAsync(Stream stream, CancellationToken cancellation = default)
161+
{
162+
ArgumentNullException.ThrowIfNull(stream);
163+
using var reader = XmlReader.Create(stream, GetSettings(true));
164+
return ReadAsync(reader, cancellation);
165+
}
166+
internal static async ValueTask<Release> ReadAsync(XmlReader reader, CancellationToken cancellation = default)
167+
{
168+
ArgumentNullException.ThrowIfNull(reader);
169+
170+
if(reader.NodeType != XmlNodeType.Element)
171+
return null;
172+
if(reader.LocalName != RELEASE_ELEMENT)
173+
return null;
174+
175+
var depth = reader.Depth;
176+
var release = new Release();
177+
PopulateAttributes(release, reader);
178+
179+
while(await reader.ReadAsync() && reader.Depth > depth)
180+
{
181+
if(reader.NodeType == XmlNodeType.Element)
182+
{
183+
switch(reader.LocalName)
184+
{
185+
case nameof(Release.Title):
186+
if(await reader.ReadAsync() && reader.NodeType == XmlNodeType.Text)
187+
release.Title = reader.Value;
188+
189+
break;
190+
case nameof(Release.Summary):
191+
if(await reader.ReadAsync() && reader.NodeType == XmlNodeType.Text)
192+
release.Summary = reader.Value;
193+
194+
break;
195+
case nameof(Release.Description):
196+
if(await reader.ReadAsync() && reader.NodeType == XmlNodeType.Text)
197+
release.Description = reader.Value;
198+
199+
break;
200+
case nameof(TAGS_ELEMENT):
201+
PopulateTags(release, reader);
202+
break;
203+
case nameof(EXECUTORS_ELEMENT):
204+
PopulateExecutors(release, reader);
205+
break;
206+
case nameof(PROPERTIES_ELEMENT):
207+
PopulateProperties(release, reader);
208+
break;
209+
}
210+
}
211+
}
212+
213+
return release;
214+
}
215+
#endregion
216+
217+
#region 私有方法
218+
static void PopulateTags(Release release, XmlReader reader)
219+
{
220+
var depth = reader.Depth;
221+
var tags = new List<string>();
222+
223+
while(reader.Read() && reader.Depth > depth)
224+
{
225+
if(reader.NodeType == XmlNodeType.Element && reader.LocalName.Equals(TAG_ELEMENT, StringComparison.OrdinalIgnoreCase))
226+
if(reader.Read() && reader.NodeType == XmlNodeType.Text && !string.IsNullOrWhiteSpace(reader.Value))
227+
tags.Add(reader.Value);
228+
}
229+
230+
if(tags != null && tags.Count > 0)
231+
release.Tags = [.. tags];
232+
}
233+
234+
static void PopulateExecutors(Release release, XmlReader reader)
235+
{
236+
var depth = reader.Depth;
237+
238+
while(reader.Read() && reader.Depth > depth)
239+
{
240+
if(reader.NodeType == XmlNodeType.Element && reader.LocalName.Equals(EXECUTOR_ELEMENT, StringComparison.OrdinalIgnoreCase))
241+
{
242+
var @event = reader.GetAttribute(EVENT_ATTRIBUTE);
243+
244+
if(string.IsNullOrWhiteSpace(@event))
245+
reader.Skip();
246+
247+
if(reader.Read() && (reader.NodeType == XmlNodeType.Text || reader.NodeType == XmlNodeType.CDATA))
248+
release.Executors.Add(new(@event, reader.Value));
249+
}
250+
}
251+
}
252+
253+
static void PopulateProperties(Release release, XmlReader reader)
254+
{
255+
var depth = reader.Depth;
256+
257+
while(reader.Read() && reader.Depth > depth)
258+
{
259+
if(reader.NodeType == XmlNodeType.Element && reader.LocalName.Equals(PROPERTY_ELEMENT, StringComparison.OrdinalIgnoreCase))
260+
{
261+
var name = reader.GetAttribute(NAME_ATTRIBUTE);
262+
263+
if(string.IsNullOrWhiteSpace(name))
264+
reader.Skip();
265+
266+
if(reader.Read() && (reader.NodeType == XmlNodeType.Text || reader.NodeType == XmlNodeType.CDATA))
267+
{
268+
var type = reader.GetAttribute(TYPE_ATTRIBUTE);
269+
270+
if(string.IsNullOrWhiteSpace(type))
271+
release.Properties.Add(name, reader.Value);
272+
else
273+
release.Properties.Add(name, Common.Convert.ConvertValue(reader.Value, TypeAlias.Parse(type)));
274+
}
275+
}
276+
}
277+
}
278+
279+
static void PopulateAttributes(Release release, XmlReader reader)
280+
{
281+
for(int i = 0; i < reader.AttributeCount; i++)
282+
{
283+
reader.MoveToAttribute(i);
284+
285+
switch(reader.LocalName)
286+
{
287+
case nameof(Release.Name):
288+
release.Name = reader.Value;
289+
break;
290+
case nameof(Release.Kind):
291+
release.Kind = Common.Convert.ConvertValue<ReleaseKind>(reader.Value);
292+
break;
293+
case nameof(Release.Title):
294+
release.Title = reader.Value;
295+
break;
296+
case nameof(Release.Edition):
297+
release.Edition = reader.Value;
298+
break;
299+
case nameof(Release.Version):
300+
release.Version = Version.Parse(reader.Value);
301+
break;
302+
case nameof(Release.Size):
303+
release.Size = uint.Parse(reader.Value);
304+
break;
305+
case nameof(Release.Path):
306+
release.Path = reader.Value;
307+
break;
308+
case nameof(Release.Checksum):
309+
release.Checksum = Checksum.Parse(reader.Value);
310+
break;
311+
case nameof(Release.Platform):
312+
release.Platform = Common.Convert.ConvertValue<Platform>(reader.Value);
313+
break;
314+
case nameof(Release.Architecture):
315+
release.Architecture = Common.Convert.ConvertValue<Architecture>(reader.Value);
316+
break;
317+
case nameof(Release.Deprecated):
318+
release.Deprecated = bool.Parse(reader.Value);
319+
break;
320+
case nameof(Release.Creation):
321+
release.Creation = DateTime.Parse(reader.Value);
322+
break;
323+
}
324+
}
325+
}
326+
327+
private static XmlReaderSettings GetSettings(bool asynchronous) => new()
328+
{
329+
Async = asynchronous,
330+
IgnoreComments = true,
331+
IgnoreWhitespace = true,
332+
IgnoreProcessingInstructions = true,
333+
};
334+
#endregion
335+
}

0 commit comments

Comments
 (0)