aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorJustin Clark-Casey (justincc)2014-08-22 20:34:33 +0100
committerJustin Clark-Casey (justincc)2014-08-22 20:34:33 +0100
commitfabab7414f346c91f5aa9a3ae3f29c262d131c6d (patch)
treed87da42fc021bc4045d24f119270b17fb5ee12dc
parentRemove query locking in MySQLUserProfileData. This is not necessary as the c... (diff)
downloadopensim-SC-fabab7414f346c91f5aa9a3ae3f29c262d131c6d.zip
opensim-SC-fabab7414f346c91f5aa9a3ae3f29c262d131c6d.tar.gz
opensim-SC-fabab7414f346c91f5aa9a3ae3f29c262d131c6d.tar.bz2
opensim-SC-fabab7414f346c91f5aa9a3ae3f29c262d131c6d.tar.xz
Remove database connection locking in MySQLXAssetData. This is unnecessary as connections aren't shared and transactions are already in place where necessary.
-rw-r--r--OpenSim/Data/MySQL/MySQLXAssetData.cs354
1 files changed, 168 insertions, 186 deletions
diff --git a/OpenSim/Data/MySQL/MySQLXAssetData.cs b/OpenSim/Data/MySQL/MySQLXAssetData.cs
index 8361da2..af7e876 100644
--- a/OpenSim/Data/MySQL/MySQLXAssetData.cs
+++ b/OpenSim/Data/MySQL/MySQLXAssetData.cs
@@ -57,7 +57,6 @@ namespace OpenSim.Data.MySQL
57 57
58 private bool m_enableCompression = false; 58 private bool m_enableCompression = false;
59 private string m_connectionString; 59 private string m_connectionString;
60 private object m_dbLock = new object();
61 60
62 /// <summary> 61 /// <summary>
63 /// We can reuse this for all hashing since all methods are single-threaded through m_dbBLock 62 /// We can reuse this for all hashing since all methods are single-threaded through m_dbBLock
@@ -131,60 +130,58 @@ namespace OpenSim.Data.MySQL
131// m_log.DebugFormat("[MYSQL XASSET DATA]: Looking for asset {0}", assetID); 130// m_log.DebugFormat("[MYSQL XASSET DATA]: Looking for asset {0}", assetID);
132 131
133 AssetBase asset = null; 132 AssetBase asset = null;
134 lock (m_dbLock) 133
134 using (MySqlConnection dbcon = new MySqlConnection(m_connectionString))
135 { 135 {
136 using (MySqlConnection dbcon = new MySqlConnection(m_connectionString)) 136 dbcon.Open();
137
138 using (MySqlCommand cmd = new MySqlCommand(
139 "SELECT Name, Description, AccessTime, AssetType, Local, Temporary, AssetFlags, CreatorID, Data FROM XAssetsMeta JOIN XAssetsData ON XAssetsMeta.Hash = XAssetsData.Hash WHERE ID=?ID",
140 dbcon))
137 { 141 {
138 dbcon.Open(); 142 cmd.Parameters.AddWithValue("?ID", assetID.ToString());
139 143
140 using (MySqlCommand cmd = new MySqlCommand( 144 try
141 "SELECT Name, Description, AccessTime, AssetType, Local, Temporary, AssetFlags, CreatorID, Data FROM XAssetsMeta JOIN XAssetsData ON XAssetsMeta.Hash = XAssetsData.Hash WHERE ID=?ID",
142 dbcon))
143 { 145 {
144 cmd.Parameters.AddWithValue("?ID", assetID.ToString()); 146 using (MySqlDataReader dbReader = cmd.ExecuteReader(CommandBehavior.SingleRow))
145
146 try
147 { 147 {
148 using (MySqlDataReader dbReader = cmd.ExecuteReader(CommandBehavior.SingleRow)) 148 if (dbReader.Read())
149 { 149 {
150 if (dbReader.Read()) 150 asset = new AssetBase(assetID, (string)dbReader["Name"], (sbyte)dbReader["AssetType"], dbReader["CreatorID"].ToString());
151 { 151 asset.Data = (byte[])dbReader["Data"];
152 asset = new AssetBase(assetID, (string)dbReader["Name"], (sbyte)dbReader["AssetType"], dbReader["CreatorID"].ToString()); 152 asset.Description = (string)dbReader["Description"];
153 asset.Data = (byte[])dbReader["Data"];
154 asset.Description = (string)dbReader["Description"];
155 153
156 string local = dbReader["Local"].ToString(); 154 string local = dbReader["Local"].ToString();
157 if (local.Equals("1") || local.Equals("true", StringComparison.InvariantCultureIgnoreCase)) 155 if (local.Equals("1") || local.Equals("true", StringComparison.InvariantCultureIgnoreCase))
158 asset.Local = true; 156 asset.Local = true;
159 else 157 else
160 asset.Local = false; 158 asset.Local = false;
161 159
162 asset.Temporary = Convert.ToBoolean(dbReader["Temporary"]); 160 asset.Temporary = Convert.ToBoolean(dbReader["Temporary"]);
163 asset.Flags = (AssetFlags)Convert.ToInt32(dbReader["AssetFlags"]); 161 asset.Flags = (AssetFlags)Convert.ToInt32(dbReader["AssetFlags"]);
164 162
165 if (m_enableCompression) 163 if (m_enableCompression)
164 {
165 using (GZipStream decompressionStream = new GZipStream(new MemoryStream(asset.Data), CompressionMode.Decompress))
166 { 166 {
167 using (GZipStream decompressionStream = new GZipStream(new MemoryStream(asset.Data), CompressionMode.Decompress)) 167 MemoryStream outputStream = new MemoryStream();
168 { 168 WebUtil.CopyStream(decompressionStream, outputStream, int.MaxValue);
169 MemoryStream outputStream = new MemoryStream(); 169// int compressedLength = asset.Data.Length;
170 WebUtil.CopyStream(decompressionStream, outputStream, int.MaxValue); 170 asset.Data = outputStream.ToArray();
171 // int compressedLength = asset.Data.Length; 171
172 asset.Data = outputStream.ToArray(); 172// m_log.DebugFormat(
173 173// "[XASSET DB]: Decompressed {0} {1} to {2} bytes from {3}",
174 // m_log.DebugFormat( 174// asset.ID, asset.Name, asset.Data.Length, compressedLength);
175 // "[XASSET DB]: Decompressed {0} {1} to {2} bytes from {3}",
176 // asset.ID, asset.Name, asset.Data.Length, compressedLength);
177 }
178 } 175 }
179
180 UpdateAccessTime(asset.Metadata, (int)dbReader["AccessTime"]);
181 } 176 }
177
178 UpdateAccessTime(asset.Metadata, (int)dbReader["AccessTime"]);
182 } 179 }
183 } 180 }
184 catch (Exception e) 181 }
185 { 182 catch (Exception e)
186 m_log.Error(string.Format("[MYSQL XASSET DATA]: Failure fetching asset {0}", assetID), e); 183 {
187 } 184 m_log.Error(string.Format("[MYSQL XASSET DATA]: Failure fetching asset {0}", assetID), e);
188 } 185 }
189 } 186 }
190 } 187 }
@@ -201,113 +198,110 @@ namespace OpenSim.Data.MySQL
201 { 198 {
202// m_log.DebugFormat("[XASSETS DB]: Storing asset {0} {1}", asset.Name, asset.ID); 199// m_log.DebugFormat("[XASSETS DB]: Storing asset {0} {1}", asset.Name, asset.ID);
203 200
204 lock (m_dbLock) 201 using (MySqlConnection dbcon = new MySqlConnection(m_connectionString))
205 { 202 {
206 using (MySqlConnection dbcon = new MySqlConnection(m_connectionString)) 203 dbcon.Open();
204
205 using (MySqlTransaction transaction = dbcon.BeginTransaction())
207 { 206 {
208 dbcon.Open(); 207 string assetName = asset.Name;
208 if (asset.Name.Length > AssetBase.MAX_ASSET_NAME)
209 {
210 assetName = asset.Name.Substring(0, AssetBase.MAX_ASSET_NAME);
211 m_log.WarnFormat(
212 "[XASSET DB]: Name '{0}' for asset {1} truncated from {2} to {3} characters on add",
213 asset.Name, asset.ID, asset.Name.Length, assetName.Length);
214 }
209 215
210 using (MySqlTransaction transaction = dbcon.BeginTransaction()) 216 string assetDescription = asset.Description;
217 if (asset.Description.Length > AssetBase.MAX_ASSET_DESC)
211 { 218 {
212 string assetName = asset.Name; 219 assetDescription = asset.Description.Substring(0, AssetBase.MAX_ASSET_DESC);
213 if (asset.Name.Length > AssetBase.MAX_ASSET_NAME) 220 m_log.WarnFormat(
214 { 221 "[XASSET DB]: Description '{0}' for asset {1} truncated from {2} to {3} characters on add",
215 assetName = asset.Name.Substring(0, AssetBase.MAX_ASSET_NAME); 222 asset.Description, asset.ID, asset.Description.Length, assetDescription.Length);
216 m_log.WarnFormat( 223 }
217 "[XASSET DB]: Name '{0}' for asset {1} truncated from {2} to {3} characters on add",
218 asset.Name, asset.ID, asset.Name.Length, assetName.Length);
219 }
220
221 string assetDescription = asset.Description;
222 if (asset.Description.Length > AssetBase.MAX_ASSET_DESC)
223 {
224 assetDescription = asset.Description.Substring(0, AssetBase.MAX_ASSET_DESC);
225 m_log.WarnFormat(
226 "[XASSET DB]: Description '{0}' for asset {1} truncated from {2} to {3} characters on add",
227 asset.Description, asset.ID, asset.Description.Length, assetDescription.Length);
228 }
229 224
230 if (m_enableCompression) 225 if (m_enableCompression)
231 { 226 {
232 MemoryStream outputStream = new MemoryStream(); 227 MemoryStream outputStream = new MemoryStream();
233 228
234 using (GZipStream compressionStream = new GZipStream(outputStream, CompressionMode.Compress, false)) 229 using (GZipStream compressionStream = new GZipStream(outputStream, CompressionMode.Compress, false))
235 { 230 {
236 // Console.WriteLine(WebUtil.CopyTo(new MemoryStream(asset.Data), compressionStream, int.MaxValue)); 231// Console.WriteLine(WebUtil.CopyTo(new MemoryStream(asset.Data), compressionStream, int.MaxValue));
237 // We have to close the compression stream in order to make sure it writes everything out to the underlying memory output stream. 232 // We have to close the compression stream in order to make sure it writes everything out to the underlying memory output stream.
238 compressionStream.Close(); 233 compressionStream.Close();
239 byte[] compressedData = outputStream.ToArray(); 234 byte[] compressedData = outputStream.ToArray();
240 asset.Data = compressedData; 235 asset.Data = compressedData;
241 }
242 } 236 }
237 }
243 238
244 byte[] hash = hasher.ComputeHash(asset.Data); 239 byte[] hash = hasher.ComputeHash(asset.Data);
245 240
246// m_log.DebugFormat( 241// m_log.DebugFormat(
247// "[XASSET DB]: Compressed data size for {0} {1}, hash {2} is {3}", 242// "[XASSET DB]: Compressed data size for {0} {1}, hash {2} is {3}",
248// asset.ID, asset.Name, hash, compressedData.Length); 243// asset.ID, asset.Name, hash, compressedData.Length);
249 244
245 try
246 {
247 using (MySqlCommand cmd =
248 new MySqlCommand(
249 "replace INTO XAssetsMeta(ID, Hash, Name, Description, AssetType, Local, Temporary, CreateTime, AccessTime, AssetFlags, CreatorID)" +
250 "VALUES(?ID, ?Hash, ?Name, ?Description, ?AssetType, ?Local, ?Temporary, ?CreateTime, ?AccessTime, ?AssetFlags, ?CreatorID)",
251 dbcon))
252 {
253 // create unix epoch time
254 int now = (int)Utils.DateTimeToUnixTime(DateTime.UtcNow);
255 cmd.Parameters.AddWithValue("?ID", asset.ID);
256 cmd.Parameters.AddWithValue("?Hash", hash);
257 cmd.Parameters.AddWithValue("?Name", assetName);
258 cmd.Parameters.AddWithValue("?Description", assetDescription);
259 cmd.Parameters.AddWithValue("?AssetType", asset.Type);
260 cmd.Parameters.AddWithValue("?Local", asset.Local);
261 cmd.Parameters.AddWithValue("?Temporary", asset.Temporary);
262 cmd.Parameters.AddWithValue("?CreateTime", now);
263 cmd.Parameters.AddWithValue("?AccessTime", now);
264 cmd.Parameters.AddWithValue("?CreatorID", asset.Metadata.CreatorID);
265 cmd.Parameters.AddWithValue("?AssetFlags", (int)asset.Flags);
266 cmd.ExecuteNonQuery();
267 }
268 }
269 catch (Exception e)
270 {
271 m_log.ErrorFormat("[ASSET DB]: MySQL failure creating asset metadata {0} with name \"{1}\". Error: {2}",
272 asset.FullID, asset.Name, e.Message);
273
274 transaction.Rollback();
275
276 return;
277 }
278
279 if (!ExistsData(dbcon, transaction, hash))
280 {
250 try 281 try
251 { 282 {
252 using (MySqlCommand cmd = 283 using (MySqlCommand cmd =
253 new MySqlCommand( 284 new MySqlCommand(
254 "replace INTO XAssetsMeta(ID, Hash, Name, Description, AssetType, Local, Temporary, CreateTime, AccessTime, AssetFlags, CreatorID)" + 285 "INSERT INTO XAssetsData(Hash, Data) VALUES(?Hash, ?Data)",
255 "VALUES(?ID, ?Hash, ?Name, ?Description, ?AssetType, ?Local, ?Temporary, ?CreateTime, ?AccessTime, ?AssetFlags, ?CreatorID)",
256 dbcon)) 286 dbcon))
257 { 287 {
258 // create unix epoch time
259 int now = (int)Utils.DateTimeToUnixTime(DateTime.UtcNow);
260 cmd.Parameters.AddWithValue("?ID", asset.ID);
261 cmd.Parameters.AddWithValue("?Hash", hash); 288 cmd.Parameters.AddWithValue("?Hash", hash);
262 cmd.Parameters.AddWithValue("?Name", assetName); 289 cmd.Parameters.AddWithValue("?Data", asset.Data);
263 cmd.Parameters.AddWithValue("?Description", assetDescription);
264 cmd.Parameters.AddWithValue("?AssetType", asset.Type);
265 cmd.Parameters.AddWithValue("?Local", asset.Local);
266 cmd.Parameters.AddWithValue("?Temporary", asset.Temporary);
267 cmd.Parameters.AddWithValue("?CreateTime", now);
268 cmd.Parameters.AddWithValue("?AccessTime", now);
269 cmd.Parameters.AddWithValue("?CreatorID", asset.Metadata.CreatorID);
270 cmd.Parameters.AddWithValue("?AssetFlags", (int)asset.Flags);
271 cmd.ExecuteNonQuery(); 290 cmd.ExecuteNonQuery();
272 } 291 }
273 } 292 }
274 catch (Exception e) 293 catch (Exception e)
275 { 294 {
276 m_log.ErrorFormat("[ASSET DB]: MySQL failure creating asset metadata {0} with name \"{1}\". Error: {2}", 295 m_log.ErrorFormat("[XASSET DB]: MySQL failure creating asset data {0} with name \"{1}\". Error: {2}",
277 asset.FullID, asset.Name, e.Message); 296 asset.FullID, asset.Name, e.Message);
278 297
279 transaction.Rollback(); 298 transaction.Rollback();
280 299
281 return; 300 return;
282 } 301 }
283
284 if (!ExistsData(dbcon, transaction, hash))
285 {
286 try
287 {
288 using (MySqlCommand cmd =
289 new MySqlCommand(
290 "INSERT INTO XAssetsData(Hash, Data) VALUES(?Hash, ?Data)",
291 dbcon))
292 {
293 cmd.Parameters.AddWithValue("?Hash", hash);
294 cmd.Parameters.AddWithValue("?Data", asset.Data);
295 cmd.ExecuteNonQuery();
296 }
297 }
298 catch (Exception e)
299 {
300 m_log.ErrorFormat("[XASSET DB]: MySQL failure creating asset data {0} with name \"{1}\". Error: {2}",
301 asset.FullID, asset.Name, e.Message);
302
303 transaction.Rollback();
304
305 return;
306 }
307 }
308
309 transaction.Commit();
310 } 302 }
303
304 transaction.Commit();
311 } 305 }
312 } 306 }
313 } 307 }
@@ -328,31 +322,28 @@ namespace OpenSim.Data.MySQL
328 if ((now - Utils.UnixTimeToDateTime(accessTime)).TotalDays < DaysBetweenAccessTimeUpdates) 322 if ((now - Utils.UnixTimeToDateTime(accessTime)).TotalDays < DaysBetweenAccessTimeUpdates)
329 return; 323 return;
330 324
331 lock (m_dbLock) 325 using (MySqlConnection dbcon = new MySqlConnection(m_connectionString))
332 { 326 {
333 using (MySqlConnection dbcon = new MySqlConnection(m_connectionString)) 327 dbcon.Open();
334 { 328 MySqlCommand cmd =
335 dbcon.Open(); 329 new MySqlCommand("update XAssetsMeta set AccessTime=?AccessTime where ID=?ID", dbcon);
336 MySqlCommand cmd =
337 new MySqlCommand("update XAssetsMeta set AccessTime=?AccessTime where ID=?ID", dbcon);
338 330
339 try 331 try
340 { 332 {
341 using (cmd) 333 using (cmd)
342 {
343 // create unix epoch time
344 cmd.Parameters.AddWithValue("?ID", assetMetadata.ID);
345 cmd.Parameters.AddWithValue("?AccessTime", (int)Utils.DateTimeToUnixTime(now));
346 cmd.ExecuteNonQuery();
347 }
348 }
349 catch (Exception)
350 { 334 {
351 m_log.ErrorFormat( 335 // create unix epoch time
352 "[XASSET MYSQL DB]: Failure updating access_time for asset {0} with name {1}", 336 cmd.Parameters.AddWithValue("?ID", assetMetadata.ID);
353 assetMetadata.ID, assetMetadata.Name); 337 cmd.Parameters.AddWithValue("?AccessTime", (int)Utils.DateTimeToUnixTime(now));
338 cmd.ExecuteNonQuery();
354 } 339 }
355 } 340 }
341 catch (Exception)
342 {
343 m_log.ErrorFormat(
344 "[XASSET MYSQL DB]: Failure updating access_time for asset {0} with name {1}",
345 assetMetadata.ID, assetMetadata.Name);
346 }
356 } 347 }
357 } 348 }
358 349
@@ -411,20 +402,17 @@ namespace OpenSim.Data.MySQL
411 string ids = "'" + string.Join("','", uuids) + "'"; 402 string ids = "'" + string.Join("','", uuids) + "'";
412 string sql = string.Format("SELECT ID FROM assets WHERE ID IN ({0})", ids); 403 string sql = string.Format("SELECT ID FROM assets WHERE ID IN ({0})", ids);
413 404
414 lock (m_dbLock) 405 using (MySqlConnection dbcon = new MySqlConnection(m_connectionString))
415 { 406 {
416 using (MySqlConnection dbcon = new MySqlConnection(m_connectionString)) 407 dbcon.Open();
408 using (MySqlCommand cmd = new MySqlCommand(sql, dbcon))
417 { 409 {
418 dbcon.Open(); 410 using (MySqlDataReader dbReader = cmd.ExecuteReader())
419 using (MySqlCommand cmd = new MySqlCommand(sql, dbcon))
420 { 411 {
421 using (MySqlDataReader dbReader = cmd.ExecuteReader()) 412 while (dbReader.Read())
422 { 413 {
423 while (dbReader.Read()) 414 UUID id = DBGuid.FromDB(dbReader["ID"]);
424 { 415 exists.Add(id);
425 UUID id = DBGuid.FromDB(dbReader["ID"]);
426 exists.Add(id);
427 }
428 } 416 }
429 } 417 }
430 } 418 }
@@ -449,43 +437,40 @@ namespace OpenSim.Data.MySQL
449 { 437 {
450 List<AssetMetadata> retList = new List<AssetMetadata>(count); 438 List<AssetMetadata> retList = new List<AssetMetadata>(count);
451 439
452 lock (m_dbLock) 440 using (MySqlConnection dbcon = new MySqlConnection(m_connectionString))
453 { 441 {
454 using (MySqlConnection dbcon = new MySqlConnection(m_connectionString)) 442 dbcon.Open();
455 { 443 MySqlCommand cmd = new MySqlCommand("SELECT Name, Description, AccessTime, AssetType, Temporary, ID, AssetFlags, CreatorID FROM XAssetsMeta LIMIT ?start, ?count", dbcon);
456 dbcon.Open(); 444 cmd.Parameters.AddWithValue("?start", start);
457 MySqlCommand cmd = new MySqlCommand("SELECT Name, Description, AccessTime, AssetType, Temporary, ID, AssetFlags, CreatorID FROM XAssetsMeta LIMIT ?start, ?count", dbcon); 445 cmd.Parameters.AddWithValue("?count", count);
458 cmd.Parameters.AddWithValue("?start", start);
459 cmd.Parameters.AddWithValue("?count", count);
460 446
461 try 447 try
448 {
449 using (MySqlDataReader dbReader = cmd.ExecuteReader())
462 { 450 {
463 using (MySqlDataReader dbReader = cmd.ExecuteReader()) 451 while (dbReader.Read())
464 { 452 {
465 while (dbReader.Read()) 453 AssetMetadata metadata = new AssetMetadata();
466 { 454 metadata.Name = (string)dbReader["Name"];
467 AssetMetadata metadata = new AssetMetadata(); 455 metadata.Description = (string)dbReader["Description"];
468 metadata.Name = (string)dbReader["Name"]; 456 metadata.Type = (sbyte)dbReader["AssetType"];
469 metadata.Description = (string)dbReader["Description"]; 457 metadata.Temporary = Convert.ToBoolean(dbReader["Temporary"]); // Not sure if this is correct.
470 metadata.Type = (sbyte)dbReader["AssetType"]; 458 metadata.Flags = (AssetFlags)Convert.ToInt32(dbReader["AssetFlags"]);
471 metadata.Temporary = Convert.ToBoolean(dbReader["Temporary"]); // Not sure if this is correct. 459 metadata.FullID = DBGuid.FromDB(dbReader["ID"]);
472 metadata.Flags = (AssetFlags)Convert.ToInt32(dbReader["AssetFlags"]); 460 metadata.CreatorID = dbReader["CreatorID"].ToString();
473 metadata.FullID = DBGuid.FromDB(dbReader["ID"]); 461
474 metadata.CreatorID = dbReader["CreatorID"].ToString(); 462 // We'll ignore this for now - it appears unused!
475
476 // We'll ignore this for now - it appears unused!
477// metadata.SHA1 = dbReader["hash"]); 463// metadata.SHA1 = dbReader["hash"]);
478 464
479 UpdateAccessTime(metadata, (int)dbReader["AccessTime"]); 465 UpdateAccessTime(metadata, (int)dbReader["AccessTime"]);
480 466
481 retList.Add(metadata); 467 retList.Add(metadata);
482 }
483 } 468 }
484 } 469 }
485 catch (Exception e) 470 }
486 { 471 catch (Exception e)
487 m_log.Error("[XASSETS DB]: MySql failure fetching asset set" + Environment.NewLine + e.ToString()); 472 {
488 } 473 m_log.Error("[XASSETS DB]: MySql failure fetching asset set" + Environment.NewLine + e.ToString());
489 } 474 }
490 } 475 }
491 476
@@ -496,21 +481,18 @@ namespace OpenSim.Data.MySQL
496 { 481 {
497// m_log.DebugFormat("[XASSETS DB]: Deleting asset {0}", id); 482// m_log.DebugFormat("[XASSETS DB]: Deleting asset {0}", id);
498 483
499 lock (m_dbLock) 484 using (MySqlConnection dbcon = new MySqlConnection(m_connectionString))
500 { 485 {
501 using (MySqlConnection dbcon = new MySqlConnection(m_connectionString)) 486 dbcon.Open();
502 {
503 dbcon.Open();
504
505 using (MySqlCommand cmd = new MySqlCommand("delete from XAssetsMeta where ID=?ID", dbcon))
506 {
507 cmd.Parameters.AddWithValue("?ID", id);
508 cmd.ExecuteNonQuery();
509 }
510 487
511 // TODO: How do we deal with data from deleted assets? Probably not easily reapable unless we 488 using (MySqlCommand cmd = new MySqlCommand("delete from XAssetsMeta where ID=?ID", dbcon))
512 // keep a reference count (?) 489 {
490 cmd.Parameters.AddWithValue("?ID", id);
491 cmd.ExecuteNonQuery();
513 } 492 }
493
494 // TODO: How do we deal with data from deleted assets? Probably not easily reapable unless we
495 // keep a reference count (?)
514 } 496 }
515 497
516 return true; 498 return true;