diff options
author | Teravus Ovares | 2009-01-19 18:33:25 +0000 |
---|---|---|
committer | Teravus Ovares | 2009-01-19 18:33:25 +0000 |
commit | 4823f2ae8e1f177d6610ee31284b3e951053587b (patch) | |
tree | 02bca4fced1d54b81f515e03d059ed9c79d506e8 /OpenSim/Region/Environment | |
parent | * minor: Just some minor log elaboration to reveal in the logs where a telepo... (diff) | |
download | opensim-SC_OLD-4823f2ae8e1f177d6610ee31284b3e951053587b.zip opensim-SC_OLD-4823f2ae8e1f177d6610ee31284b3e951053587b.tar.gz opensim-SC_OLD-4823f2ae8e1f177d6610ee31284b3e951053587b.tar.bz2 opensim-SC_OLD-4823f2ae8e1f177d6610ee31284b3e951053587b.tar.xz |
* Set SVN Properties
Diffstat (limited to 'OpenSim/Region/Environment')
-rw-r--r-- | OpenSim/Region/Environment/Interfaces/IJ2KDecoder.cs | 80 | ||||
-rw-r--r-- | OpenSim/Region/Environment/Modules/Agent/TextureSender/J2KDecoderModule.cs | 430 |
2 files changed, 255 insertions, 255 deletions
diff --git a/OpenSim/Region/Environment/Interfaces/IJ2KDecoder.cs b/OpenSim/Region/Environment/Interfaces/IJ2KDecoder.cs index f0e13ba..44b9289 100644 --- a/OpenSim/Region/Environment/Interfaces/IJ2KDecoder.cs +++ b/OpenSim/Region/Environment/Interfaces/IJ2KDecoder.cs | |||
@@ -1,40 +1,40 @@ | |||
1 | /* | 1 | /* |
2 | * Copyright (c) Contributors, http://opensimulator.org/ | 2 | * Copyright (c) Contributors, http://opensimulator.org/ |
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | 3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. |
4 | * | 4 | * |
5 | * Redistribution and use in source and binary forms, with or without | 5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions are met: | 6 | * modification, are permitted provided that the following conditions are met: |
7 | * * Redistributions of source code must retain the above copyright | 7 | * * Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. | 8 | * notice, this list of conditions and the following disclaimer. |
9 | * * Redistributions in binary form must reproduce the above copyright | 9 | * * Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the | 10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. | 11 | * documentation and/or other materials provided with the distribution. |
12 | * * Neither the name of the OpenSim Project nor the | 12 | * * Neither the name of the OpenSim Project nor the |
13 | * names of its contributors may be used to endorse or promote products | 13 | * names of its contributors may be used to endorse or promote products |
14 | * derived from this software without specific prior written permission. | 14 | * derived from this software without specific prior written permission. |
15 | * | 15 | * |
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | 16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY |
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | 17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | 19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY |
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | */ | 26 | */ |
27 | 27 | ||
28 | using OpenMetaverse; | 28 | using OpenMetaverse; |
29 | using OpenMetaverse.Imaging; | 29 | using OpenMetaverse.Imaging; |
30 | 30 | ||
31 | namespace OpenSim.Region.Environment.Interfaces | 31 | namespace OpenSim.Region.Environment.Interfaces |
32 | { | 32 | { |
33 | 33 | ||
34 | public delegate void DecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers); | 34 | public delegate void DecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers); |
35 | 35 | ||
36 | public interface IJ2KDecoder | 36 | public interface IJ2KDecoder |
37 | { | 37 | { |
38 | void decode(UUID AssetId, byte[] assetData, DecodedCallback decodedReturn); | 38 | void decode(UUID AssetId, byte[] assetData, DecodedCallback decodedReturn); |
39 | } | 39 | } |
40 | } | 40 | } |
diff --git a/OpenSim/Region/Environment/Modules/Agent/TextureSender/J2KDecoderModule.cs b/OpenSim/Region/Environment/Modules/Agent/TextureSender/J2KDecoderModule.cs index 7c51d68..6b84880 100644 --- a/OpenSim/Region/Environment/Modules/Agent/TextureSender/J2KDecoderModule.cs +++ b/OpenSim/Region/Environment/Modules/Agent/TextureSender/J2KDecoderModule.cs | |||
@@ -1,215 +1,215 @@ | |||
1 | /* | 1 | /* |
2 | * Copyright (c) Contributors, http://opensimulator.org/ | 2 | * Copyright (c) Contributors, http://opensimulator.org/ |
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | 3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. |
4 | * | 4 | * |
5 | * Redistribution and use in source and binary forms, with or without | 5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions are met: | 6 | * modification, are permitted provided that the following conditions are met: |
7 | * * Redistributions of source code must retain the above copyright | 7 | * * Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. | 8 | * notice, this list of conditions and the following disclaimer. |
9 | * * Redistributions in binary form must reproduce the above copyright | 9 | * * Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the | 10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. | 11 | * documentation and/or other materials provided with the distribution. |
12 | * * Neither the name of the OpenSim Project nor the | 12 | * * Neither the name of the OpenSim Project nor the |
13 | * names of its contributors may be used to endorse or promote products | 13 | * names of its contributors may be used to endorse or promote products |
14 | * derived from this software without specific prior written permission. | 14 | * derived from this software without specific prior written permission. |
15 | * | 15 | * |
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | 16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY |
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | 17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | 19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY |
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | */ | 26 | */ |
27 | 27 | ||
28 | using System; | 28 | using System; |
29 | using System.Reflection; | 29 | using System.Reflection; |
30 | using System.Threading; | 30 | using System.Threading; |
31 | using System.Collections.Generic; | 31 | using System.Collections.Generic; |
32 | using log4net; | 32 | using log4net; |
33 | using Nini.Config; | 33 | using Nini.Config; |
34 | using OpenMetaverse; | 34 | using OpenMetaverse; |
35 | using OpenMetaverse.Imaging; | 35 | using OpenMetaverse.Imaging; |
36 | using OpenSim.Region.Environment.Interfaces; | 36 | using OpenSim.Region.Environment.Interfaces; |
37 | using OpenSim.Region.Environment.Scenes; | 37 | using OpenSim.Region.Environment.Scenes; |
38 | 38 | ||
39 | namespace OpenSim.Region.Environment.Modules.Agent.TextureSender | 39 | namespace OpenSim.Region.Environment.Modules.Agent.TextureSender |
40 | { | 40 | { |
41 | public class J2KDecoderModule : IRegionModule, IJ2KDecoder | 41 | public class J2KDecoderModule : IRegionModule, IJ2KDecoder |
42 | { | 42 | { |
43 | #region IRegionModule Members | 43 | #region IRegionModule Members |
44 | 44 | ||
45 | private static readonly ILog m_log | 45 | private static readonly ILog m_log |
46 | = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 46 | = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
47 | 47 | ||
48 | /// <summary> | 48 | /// <summary> |
49 | /// Cached Decoded Layers | 49 | /// Cached Decoded Layers |
50 | /// </summary> | 50 | /// </summary> |
51 | private readonly Dictionary<UUID, OpenJPEG.J2KLayerInfo[]> m_cacheddecode = new Dictionary<UUID, OpenJPEG.J2KLayerInfo[]>(); | 51 | private readonly Dictionary<UUID, OpenJPEG.J2KLayerInfo[]> m_cacheddecode = new Dictionary<UUID, OpenJPEG.J2KLayerInfo[]>(); |
52 | 52 | ||
53 | /// <summary> | 53 | /// <summary> |
54 | /// List of client methods to notify of results of decode | 54 | /// List of client methods to notify of results of decode |
55 | /// </summary> | 55 | /// </summary> |
56 | private readonly Dictionary<UUID, List<DecodedCallback>> m_notifyList = new Dictionary<UUID, List<DecodedCallback>>(); | 56 | private readonly Dictionary<UUID, List<DecodedCallback>> m_notifyList = new Dictionary<UUID, List<DecodedCallback>>(); |
57 | 57 | ||
58 | public void Initialise(Scene scene, IConfigSource source) | 58 | public void Initialise(Scene scene, IConfigSource source) |
59 | { | 59 | { |
60 | scene.RegisterModuleInterface<IJ2KDecoder>(this); | 60 | scene.RegisterModuleInterface<IJ2KDecoder>(this); |
61 | } | 61 | } |
62 | 62 | ||
63 | public void PostInitialise() | 63 | public void PostInitialise() |
64 | { | 64 | { |
65 | 65 | ||
66 | } | 66 | } |
67 | 67 | ||
68 | public void Close() | 68 | public void Close() |
69 | { | 69 | { |
70 | 70 | ||
71 | } | 71 | } |
72 | 72 | ||
73 | public string Name | 73 | public string Name |
74 | { | 74 | { |
75 | get { return "J2KDecoderModule"; } | 75 | get { return "J2KDecoderModule"; } |
76 | } | 76 | } |
77 | 77 | ||
78 | public bool IsSharedModule | 78 | public bool IsSharedModule |
79 | { | 79 | { |
80 | get { return true; } | 80 | get { return true; } |
81 | } | 81 | } |
82 | 82 | ||
83 | #endregion | 83 | #endregion |
84 | 84 | ||
85 | #region IJ2KDecoder Members | 85 | #region IJ2KDecoder Members |
86 | 86 | ||
87 | 87 | ||
88 | public void decode(UUID AssetId, byte[] assetData, DecodedCallback decodedReturn) | 88 | public void decode(UUID AssetId, byte[] assetData, DecodedCallback decodedReturn) |
89 | { | 89 | { |
90 | // Dummy for if decoding fails. | 90 | // Dummy for if decoding fails. |
91 | OpenJPEG.J2KLayerInfo[] result = new OpenJPEG.J2KLayerInfo[0]; | 91 | OpenJPEG.J2KLayerInfo[] result = new OpenJPEG.J2KLayerInfo[0]; |
92 | 92 | ||
93 | // Check if it's cached | 93 | // Check if it's cached |
94 | bool cached = false; | 94 | bool cached = false; |
95 | lock (m_cacheddecode) | 95 | lock (m_cacheddecode) |
96 | { | 96 | { |
97 | if (m_cacheddecode.ContainsKey(AssetId)) | 97 | if (m_cacheddecode.ContainsKey(AssetId)) |
98 | { | 98 | { |
99 | cached = true; | 99 | cached = true; |
100 | result = m_cacheddecode[AssetId]; | 100 | result = m_cacheddecode[AssetId]; |
101 | } | 101 | } |
102 | } | 102 | } |
103 | 103 | ||
104 | // If it's cached, return the cached results | 104 | // If it's cached, return the cached results |
105 | if (cached) | 105 | if (cached) |
106 | { | 106 | { |
107 | decodedReturn(AssetId, result); | 107 | decodedReturn(AssetId, result); |
108 | } | 108 | } |
109 | else | 109 | else |
110 | { | 110 | { |
111 | // not cached, so we need to decode it | 111 | // not cached, so we need to decode it |
112 | // Add to notify list and start decoding. | 112 | // Add to notify list and start decoding. |
113 | // Next request for this asset while it's decoding will only be added to the notify list | 113 | // Next request for this asset while it's decoding will only be added to the notify list |
114 | // once this is decoded, requests will be served from the cache and all clients in the notifylist will be updated | 114 | // once this is decoded, requests will be served from the cache and all clients in the notifylist will be updated |
115 | bool decode = false; | 115 | bool decode = false; |
116 | lock (m_notifyList) | 116 | lock (m_notifyList) |
117 | { | 117 | { |
118 | if (m_notifyList.ContainsKey(AssetId)) | 118 | if (m_notifyList.ContainsKey(AssetId)) |
119 | { | 119 | { |
120 | m_notifyList[AssetId].Add(decodedReturn); | 120 | m_notifyList[AssetId].Add(decodedReturn); |
121 | } | 121 | } |
122 | else | 122 | else |
123 | { | 123 | { |
124 | List<DecodedCallback> notifylist = new List<DecodedCallback>(); | 124 | List<DecodedCallback> notifylist = new List<DecodedCallback>(); |
125 | notifylist.Add(decodedReturn); | 125 | notifylist.Add(decodedReturn); |
126 | m_notifyList.Add(AssetId, notifylist); | 126 | m_notifyList.Add(AssetId, notifylist); |
127 | decode = true; | 127 | decode = true; |
128 | } | 128 | } |
129 | } | 129 | } |
130 | // Do Decode! | 130 | // Do Decode! |
131 | if (decode) | 131 | if (decode) |
132 | { | 132 | { |
133 | doJ2kDecode(AssetId, assetData); | 133 | doJ2kDecode(AssetId, assetData); |
134 | } | 134 | } |
135 | } | 135 | } |
136 | } | 136 | } |
137 | 137 | ||
138 | #endregion | 138 | #endregion |
139 | 139 | ||
140 | /// <summary> | 140 | /// <summary> |
141 | /// Decode Jpeg2000 Asset Data | 141 | /// Decode Jpeg2000 Asset Data |
142 | /// </summary> | 142 | /// </summary> |
143 | /// <param name="AssetId">UUID of Asset</param> | 143 | /// <param name="AssetId">UUID of Asset</param> |
144 | /// <param name="j2kdata">Byte Array Asset Data </param> | 144 | /// <param name="j2kdata">Byte Array Asset Data </param> |
145 | private void doJ2kDecode(UUID AssetId, byte[] j2kdata) | 145 | private void doJ2kDecode(UUID AssetId, byte[] j2kdata) |
146 | { | 146 | { |
147 | int DecodeTime = 0; | 147 | int DecodeTime = 0; |
148 | DecodeTime = System.Environment.TickCount; | 148 | DecodeTime = System.Environment.TickCount; |
149 | OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[0]; // Dummy result for if it fails. Informs that there's only full quality | 149 | OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[0]; // Dummy result for if it fails. Informs that there's only full quality |
150 | try | 150 | try |
151 | { | 151 | { |
152 | 152 | ||
153 | AssetTexture texture = new AssetTexture(AssetId, j2kdata); | 153 | AssetTexture texture = new AssetTexture(AssetId, j2kdata); |
154 | if (texture.DecodeLayerBoundaries()) | 154 | if (texture.DecodeLayerBoundaries()) |
155 | { | 155 | { |
156 | bool sane = true; | 156 | bool sane = true; |
157 | 157 | ||
158 | // Sanity check all of the layers | 158 | // Sanity check all of the layers |
159 | for (int i = 0; i < texture.LayerInfo.Length; i++) | 159 | for (int i = 0; i < texture.LayerInfo.Length; i++) |
160 | { | 160 | { |
161 | if (texture.LayerInfo[i].End > texture.AssetData.Length) | 161 | if (texture.LayerInfo[i].End > texture.AssetData.Length) |
162 | { | 162 | { |
163 | sane = false; | 163 | sane = false; |
164 | break; | 164 | break; |
165 | } | 165 | } |
166 | } | 166 | } |
167 | 167 | ||
168 | if (sane) | 168 | if (sane) |
169 | { | 169 | { |
170 | layers = texture.LayerInfo; | 170 | layers = texture.LayerInfo; |
171 | } | 171 | } |
172 | else | 172 | else |
173 | { | 173 | { |
174 | m_log.WarnFormat("[J2KDecoderModule]: JPEG2000 texture decoding succeeded, but sanity check failed for {0}", | 174 | m_log.WarnFormat("[J2KDecoderModule]: JPEG2000 texture decoding succeeded, but sanity check failed for {0}", |
175 | AssetId); | 175 | AssetId); |
176 | } | 176 | } |
177 | } | 177 | } |
178 | 178 | ||
179 | else | 179 | else |
180 | { | 180 | { |
181 | m_log.WarnFormat("[J2KDecoderModule]: JPEG2000 texture decoding failed for {0}", AssetId); | 181 | m_log.WarnFormat("[J2KDecoderModule]: JPEG2000 texture decoding failed for {0}", AssetId); |
182 | } | 182 | } |
183 | texture = null; // dereference and dispose of ManagedImage | 183 | texture = null; // dereference and dispose of ManagedImage |
184 | } | 184 | } |
185 | catch (Exception ex) | 185 | catch (Exception ex) |
186 | { | 186 | { |
187 | m_log.WarnFormat("[J2KDecoderModule]: JPEG2000 texture decoding threw an exception for {0}, {1}", AssetId, ex); | 187 | m_log.WarnFormat("[J2KDecoderModule]: JPEG2000 texture decoding threw an exception for {0}, {1}", AssetId, ex); |
188 | } | 188 | } |
189 | 189 | ||
190 | // Write out decode time | 190 | // Write out decode time |
191 | m_log.InfoFormat("[J2KDecoderModule]: {0} Decode Time: {1}", System.Environment.TickCount - DecodeTime, AssetId); | 191 | m_log.InfoFormat("[J2KDecoderModule]: {0} Decode Time: {1}", System.Environment.TickCount - DecodeTime, AssetId); |
192 | 192 | ||
193 | // Cache Decoded layers | 193 | // Cache Decoded layers |
194 | lock (m_cacheddecode) | 194 | lock (m_cacheddecode) |
195 | { | 195 | { |
196 | m_cacheddecode.Add(AssetId, layers); | 196 | m_cacheddecode.Add(AssetId, layers); |
197 | 197 | ||
198 | } | 198 | } |
199 | 199 | ||
200 | // Notify Interested Parties | 200 | // Notify Interested Parties |
201 | lock (m_notifyList) | 201 | lock (m_notifyList) |
202 | { | 202 | { |
203 | if (m_notifyList.ContainsKey(AssetId)) | 203 | if (m_notifyList.ContainsKey(AssetId)) |
204 | { | 204 | { |
205 | foreach (DecodedCallback d in m_notifyList[AssetId]) | 205 | foreach (DecodedCallback d in m_notifyList[AssetId]) |
206 | { | 206 | { |
207 | if (d != null) | 207 | if (d != null) |
208 | d.DynamicInvoke(AssetId, layers); | 208 | d.DynamicInvoke(AssetId, layers); |
209 | } | 209 | } |
210 | m_notifyList.Remove(AssetId); | 210 | m_notifyList.Remove(AssetId); |
211 | } | 211 | } |
212 | } | 212 | } |
213 | } | 213 | } |
214 | } | 214 | } |
215 | } | 215 | } |